I have swapped some of my Sprite objects in favour of Meshes without textures as they should take up less memory.
However, when checking my Profiler, even after the swap the used texture memory is all the same.
So I decided to unload those textures forcefully but things aren't going very well.
My intention with this code is to find all the textures by the name of the GameObject they were on (as I just dragged the sprites on the scene via inspector, their name matches. I know which GO-s to look for as only they contain a MeshFilter) and then unload them by hand.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
public class DumpUnused : MonoBehaviour
{
private TextureContainer swappedTextures = new TextureContainer();
private IEnumerator Start()
{
RemoveSwappedTextures();
var unloader = Resources.UnloadUnusedAssets();
while (!unloader.isDone)
{
yield return null;
}
}
private void RemoveSwappedTextures()
{
//getting all my scene objects that has a mesh filter.
/*
* NOTE: I swapped my sprites for generated meshes.
* However, the texture memory used didn't decrease so I bet they are still loaded.
* The textures I used as sprites had the same name as the assets themselves as assets were
* just dragged on the scene.
*/
var sceneObjects =
UnityEngine.Object.FindObjectsOfType<GameObject>()
.Where(g => g.GetComponent<MeshFilter>() != null);
//we have the repaced gameobjects.
//now to get the used textures by their names:
foreach (var item in sceneObjects)
{
//getting everything, even assets
var strToFind = GetOriginalName(item.name);
swappedTextures.Append(
Resources.FindObjectsOfTypeAll<Texture2D>()
.Where(t => t.name.Equals(strToFind)
));
}
foreach (var text2Unload in swappedTextures)
{
//destroy the loaded textures.
DestroyImmediate(text2Unload);
}
swappedTextures = null;
}
private string GetOriginalName(string name)
{
/*
* getting the original asset name by the "unity populated name".
* reason: say I have a "backGr" named texture.
* if I copy paste it all over the place, unity makes their name
* "backGr (1)", "backGr (2)" and so on.
* So I search until the first whitespace and anything before that
* is the original name of the texture.
*/
string str = "";
char c;int i = 0;
for (c = name[i]; i < name.Length && c != ' '; i++)
{
str += c;
}
return str;
}
public class TextureContainer : List<Texture2D>
{
//made only for convenience
public void Append(IEnumerable<Texture2D> textures)
{
foreach (var _t in textures)
{
this.Add(_t);
}
}
}
}
The reason I guess it doesn't do what I want is because the used texture memory is all the same and used total memory is even higher than it was before.
So, unless it is normal for Unity to use more texture memory when there's less textures in the scene, I guess I messed up somewhere.
The question is, how to make this work? If I'm on the wrong road, what are the best practices to Unload textures by name?
Related
I am working on a project where I have a script that combines all the meshes of a gameobjects childs into one mesh while in edit mode. Now the problem is that even though I have managed to then save the mesh into my assets folder it is only saved as a default asset(blank white icon) and not as a mesh that I can then use for other things. Now I would like to know how to get it saved as a mesh asset. I think I could either somehow convert the default asset file to a mesh asset if that is possible, or I could directly save an objects mesh into the asset folder but I dont know if that's possible.
I would be very grateful if you could share code that I could use to do this(it doesnt have to be fance, it should just work).
Here is the code that I have currently:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
[ExecuteInEditMode]
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
// Copy meshes from children into the parent's Mesh.
// CombineInstance stores the list of meshes. These are combined
// and assigned to the attached Mesh.
public class CombineMeshes : EditorWindow
{
[Header("References")]
public GameObject generateFromObject;
[MenuItem("Window/My/MeshGenerator")]
static void Init()
{
CombineMeshes window = (CombineMeshes)GetWindow(typeof(CombineMeshes));
}
private void OnGUI()
{
generateFromObject = (GameObject)EditorGUILayout.ObjectField(generateFromObject, typeof(GameObject), true);
if (GUILayout.Button("Generate"))
{
Generate();
}
}
void Generate()
{
MeshFilter[] meshFilters = generateFromObject.GetComponentsInChildren<MeshFilter>();
CombineInstance[] combine = new CombineInstance[meshFilters.Length];
int i = 0;
while (i < meshFilters.Length)
{
combine[i].mesh = meshFilters[i].sharedMesh;
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
i++;
}
generateFromObject.transform.GetComponent<MeshFilter>().mesh = new Mesh();
generateFromObject.transform.GetComponent<MeshFilter>().mesh.CombineMeshes(combine);
generateFromObject.transform.gameObject.SetActive(true);
Mesh mesh = generateFromObject.transform.GetComponent<MeshFilter>().mesh;
AssetDatabase.CreateAsset(mesh, "Assets/myAsset");
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(mesh));
AssetDatabase.SaveAssets();
}
}
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");
i am creating a video game like Crypt of the necrodancer and i am blocked when i want to generate a map
my idea was simple :
make a grid system and replace some tiles to have specific tiles (obstacle, wall...) but i don't know how i should store the information to replace my tile.
I based my grid generation map from Sebastian Lague tutorial
i would like to replace in the inspector my tiles
as far as i am, i've found a script for replacing some tiles by another but i can't save it...
Here is the script for replacing stuff
using UnityEngine;
using UnityEditor;
public class ReplaceWithPrefab : EditorWindow
{
[SerializeField] private GameObject prefab;
[MenuItem("Tools/Replace With Prefab")]
static void CreateReplaceWithPrefab()
{
EditorWindow.GetWindow<ReplaceWithPrefab>();
}
private void OnGUI()
{
prefab = (GameObject)EditorGUILayout.ObjectField("Prefab", prefab, typeof(GameObject), false);
if (GUILayout.Button("Replace"))
{
var selection = Selection.gameObjects;
for (var i = selection.Length - 1; i >= 0; --i)
{
var selected = selection[i];
var prefabType = PrefabUtility.GetPrefabType(prefab);
GameObject newObject;
if (prefabType == PrefabType.Prefab)
{
newObject = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
}
else
{
newObject = Instantiate(prefab);
newObject.name = prefab.name;
}
if (newObject == null)
{
Debug.LogError("Error instantiating prefab");
break;
}
Undo.RegisterCreatedObjectUndo(newObject, "Replace With Prefabs");
newObject.transform.parent = selected.transform.parent;
newObject.transform.localPosition = selected.transform.localPosition;
newObject.transform.localRotation = selected.transform.localRotation;
newObject.transform.localScale = selected.transform.localScale;
newObject.transform.SetSiblingIndex(selected.transform.GetSiblingIndex());
Undo.DestroyObjectImmediate(selected);
}
}
GUI.enabled = false;
EditorGUILayout.LabelField("Selection count: " + Selection.objects.Length);
}
}
any ideas for how i could save the informations?
Unity recently added a tilemap system a few versions ago. It seems to be what you're looking for and it already handles the grids for you.
You can read more about it here:
https://docs.unity3d.com/Manual/class-Tilemap.html
You can edit the tilemap with the SetTile method and a Tile object, rather than instanciating a bunch of prefabs. The Tiles themselves are created with a tile palette which you can also read more about in the page above.
In my Unity Project I've C# code to get Prefabs preview image in png format.
In Unity Play mode I have not errors with this Script and everything it's ok, but when I trie to build my project I've receive error.
I spend a lot of times to try understand where I'd made a mistake, but no result.
Can some body help with this problem?
C# Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO;
using System;
public class LoadTexture : MonoBehaviour
{
[System.Serializable]
public class LoadTex
{
public string name;
public GameObject texture;
public Texture2D gameObjectTex;
public Texture gameObjTex;
}
public List<LoadTex> ItemTablezz = new List<LoadTex>();
// Start is called before the first frame update
void Start()
{
for (int i = 0; i < ItemTablezz.Count; i++)
{
var getImage = UnityEditor.AssetPreview.GetAssetPreview(ItemTablezz[i].texture);
print(getImage);
ItemTablezz[i].gameObjectTex = getImage;
}
}
// Update is called once per frame
void Update()
{
CheckIfNull();
}
void CheckIfNull()
{
for (int k = 0; k < ItemTablezz.Count; k++)
{
Texture2D tex = new Texture2D(128, 128, TextureFormat.RGBA32, false);
Color[] colors = ItemTablezz[k].gameObjectTex.GetPixels();
int i = 0;
Color alpha = colors[i];
for (; i < colors.Length; i++)
{
if (colors[i] == alpha)
{
colors[i].a = 0;
}
}
tex.SetPixels(colors);
tex.Apply();
byte[] png = tex.EncodeToPNG();
File.WriteAllBytes(Application.persistentDataPath + "/" + ItemTablezz[k].name + ".png", png);
}
}
}
error CS0103: The name 'AssetPreview' does not exist in the current context
Where I'd made mistake?
UnityEditor.AssetPreview belongs to the UnityEditor namespace.
This only exists in the Unity Editor istelf and is stripped of in a build.
=> You can't use anything fromt the UnityEditor namespace in a build.
There are basically two solutions in order to exclude UnityEditor stuff from builds:
Pre-processors
In c# you can use the #if preprocessor in order to exclude code blocks depending on global defines. Unity offers such defines. In this case use e.g.
#if UNITY_EDITOR
// CODE USING UnityEditor
#endif
Editor folder
If an entire script shall be excluded from a build you can simply place it in a folder named Editor. This will make it completely stripped of from a build.
For using this in a build you would either have to use another library or run this script once within the Unity Editor and store those references for using them in a build e.g. using the [ContextMenu] attribute:
void Start()
{
#if UNITY_EDITOR
LoadPreviewImages();
#endif
// if nothing more comes here
// rather remove this method entirely
...
}
#if UNITY_EDITOR
// This allows you to call this method
// from the according components context menu in the Inspector
[ContextMenu("LoadPreviewImages")]
private void LoadPreviewImages()
{
foreach (var loadText in ItemTablezz)
{
var getImage = UnityEditor.AssetPreview.GetAssetPreview(loadText.texture);
print(getImage);
loadText.gameObjectTex = getImage;
}
}
#endif
I'm working on a personal project in which I want to visualize airplanes above airports. I created a .csv file containing the coordinates of one airplane over a certain period of time. I tried to write a code in Unity in which the coordinates are linked to a cube and moves over time. Unfortunately my code does not work.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO
public class AirplanePlayer : MonoBehaviour
{
public GameObject airplane;
public TextAsset csvFile;
// Update is called once per frame
void Update() {
}
void readCSV()
{
string[] records = csvFile.text.Split('\n');
for(int i = 0; i < records; i++)
{airplane.transform.position(float.Parse(fields[2]),float.Parse(fields[3]), float.Parse(fields[4]));
}
}
}
Expected results would be a cube which moves in different directions over time. Would love some tips, thank you in advance!
To move the plane between points you could use the Vector3.MoveTowards method.
Here is a very basic implementation of what I understand you are trying to accomplish:
public class PlaneController : MonoBehaviour
{
public TextAsset coordinates;
public int moveSpeed;
string[] coordinatesArray;
int currentPointIndex = 0;
Vector3 destinationVector;
void Start()
{
coordinatesArray = coordinates.text.Split(new char[] { '\n' });
}
void Update()
{
if (destinationVector == null || transform.position == destinationVector)
{
currentPointIndex = currentPointIndex < coordinatesArray.Length - 1 ? currentPointIndex + 1 : 1;
if(!string.IsNullOrWhiteSpace(coordinatesArray[currentPointIndex]))
{
string[] xyz = coordinatesArray[currentPointIndex].Split(new char[] { ',' });
destinationVector = new Vector3(float.Parse(xyz[0]), float.Parse(xyz[1]), float.Parse(xyz[1]));
}
}
else
{
transform.position = Vector3.MoveTowards(transform.localPosition, destinationVector, Time.deltaTime * moveSpeed);
}
}
}
I also made it do a little loop with the coordinates and added a speed property.
I'm not really sure if adding the csv file as public TextAsset for the game object is the right approach, maybe it makes more sense to use the file path for the csv file instead and get the file data from code.
Hope this helps, let me know if you have any more questions.