I'm struggling with an issue in Unity loading Sprites from a SpriteAtlas, downloaded in an AssetBundle.
In our current game I am trying to implement AssetBundles to remove "Resources" folder usage, and reduce memory overhead (among other things).
In the Game app, the downloaded sprites aren't rendering correctly when running in the editor, so I built a small test project to better understand the problem. Unfortunately the test project works perfectly, even though I'm using identical code to download and display the sprites. I'll refer to these two versions as TestApp and GameApp from here on. Just to reiterate, this issue is only a problem when running in the Editor (not the final device builds), however this is a game breaker for us because we simply can't develop and test the application. The turnaround getting builds to device is simply too long compared to running in the Editor.
A simplified version of the script that I use for loading asset bundles is as follows. (This has been hugely simplified for brievity, including stripping out all object caching and error handling, etc)
public IEnumerator GetSpriteFromBundle(string bundleURL, string spriteAtlasName, string spriteName, Action<Sprite> onLoadAction)
{
// get the AssetBundle
AssetBundle bundle = null;
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(bundleURL);
yield return request.SendWebRequest();
if (!request.isNetworkError && !request.isHttpError)
{
bundle = DownloadHandlerAssetBundle.GetContent(request);
}
// Get the SpriteAtlas
SpriteAtlas atlas = null;
if (bundle != null)
{
if (bundle.Contains(spriteAtlasName))
{
AssetBundleRequest assetRequest = bundle.LoadAssetAsync<SpriteAtlas>(spriteAtlasName);
yield return assetRequest;
if (assetRequest.isDone)
{
atlas = assetRequest.asset as SpriteAtlas;
}
}
}
// Get the Sprite
Sprite sprite = null;
if (atlas != null)
{
sprite = atlas.GetSprite(spriteName);
}
onLoadAction(sprite);
}
The script that I use to call this to load the Sprite is as follows, (again error handling is stripped out):
public void Start()
{
UnityEngine.UI.Image displayImage = GameObject.Find("Path/To/ImageObject").GetComponent<UnityEngine.UI.Image>();
StartCoroutine(
GetSpriteFromBundle(
"https://mycdn.com/myassetbundles/gamesprites", // AssetBundleURL
"GameSprites", // SpriteAssetName
"Icon1", // SpriteName
(sprite) =>
{
displayImage.sprite = sprite;
})
);
}
The end result of this is that everything works and loads correctly in the TestApp, but when playing the GameApp in the editor, the sprites are either invisible, or display as a weird image with 3 squares in it.
The only difference that I can see is that when I use the frame debugger to look at the differences between the TestApp and the GameApp, the TestApp shows the SpriteAtlas texture in the batch, but the GameApp does not.
As you can see here in the TestApp, the Texture is correctly set.
And here in the GameApp, the texture is not set
Things that I have checked and confirmed between versions
Neither the GameApp nor the TestApp has any errors or exceptions.
It works correctly when built and deployed to a device (Only tested on
Android so far)
A sprite object IS being returned in the onLoadAction callback in the GameApp.
I'm using the same AssetBundles and Sprites in both applications.
I've done side by side comparisons of the Image object settings in the inspector in both apps.
Both apps are set to the same build platform (I've tried Android, WebGL, and
StandaloneWindows, and all have the same result)
The AssetBundles are
built for the correct build platform (as above)
The only difference that I can see between the TestApp and the GameApp is that the GameApp is much larger / more complex, and it has a scene change (we start with a loading scene before going to the in-game scene), but I don't see how either of those should affect anything.
I've also set up and tested a version using AssetBundle.LoadFromFileAsync() and loading the file from the StreamingAssets folder, with the same results
So, my questions:
Is this a bug in the Unity Editor? What should I be looking at to try and fix this? We basically can't use AssetBundles until I find a solution.
I've used the AssetBundleBrowser asset to set up the AssetBundles.
I've tested with various versions of Unity, from older 2018.1 releases up to the latest release (2018.2.7f1 at the time of writing).
(Cross posted from the Unity Forums)
--- Update ---
It's been mentioned that this is a duplicate question this question , however I am asking an entirely different question.
My code works correctly on a device, but does not work in the Unity Editor.
I have also tried restructuring my code to query for a Sprite rather than a SpriteAtlas, and using the LoadAssetWithSubAssetsAsync method, with the following code, and I am still having the same end result of no sprite being displayed in the editor.
private IEnumerator GetSpriteFromBundle(string bundleURL, string spriteName, Action<Sprite> onLoadAction)
{
// get the AssetBundle
AssetBundle bundle = null;
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(bundleURL);
yield return request.SendWebRequest();
if (!request.isNetworkError && !request.isHttpError)
{
bundle = DownloadHandlerAssetBundle.GetContent(request);
}
// Get the Sprite
Sprite sprite = null;
if (bundle != null)
{
if (bundle.Contains(spriteName))
{
AssetBundleRequest assetRequest = bundle.LoadAssetWithSubAssetsAsync<Sprite>(spriteName);
yield return assetRequest;
if (assetRequest.isDone)
{
for (int i = 0; i < assetRequest.allAssets.Length; i++)
{
sprite = assetRequest.allAssets[i] as Sprite;
if (sprite != null && sprite.name == spriteName)
{
onLoadAction(sprite);
yield break;
}
}
}
}
}
onLoadAction(null);
}
It turns out that the problem was caused by the SpritePacker settings.
If I set the SpritePacker mode (in Edit->Project Settings->Editor) to "Enabled for Builds" then the sprites aren't loaded properly, whereas if I set it to "Always Enabled" (the default I believe) then the sprites, and the SpriteAtlas is loaded correctly from the AssetBundle.
(I've raised this as a bug with Unity, but haven't heard a response yet).
Related
Unity ver. 2019.4.19
Sorry for editing this question several time, I didn't use the term editor and edit mode correctly and I worte the code wrong. Now I've fixed.
I'm using timeline in my project. I made a custom clip like this to change some properties in matrial.
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
GameObject target = GetTargetGameObject();
var renderers = target.GetComponentsInChildren<Renderer>();
foreach (var r in renderers)
{
if (!Application.isPlaying)
{
foreach (var mat in r.sharedMaterials)
{
mat.SetFloat("_SomeProperty", SomeValueChangedByPlaytime(data));
}
}
else
{
// in play mode use r.materials instead
}
}
}
I want to check the effects in timeline while I'm in edit mode, but I don't want to change my material assets by my code.
If I Use
r.sharedMaterials
This will change all matrial assets and this may cause some unexpected effect on unintended objects.
If I Use
r.materials
This will cause error "Instantiating material due to calling renderer.material during edit mode"
So how can I avoid changing matrial asset while still get correct preview in timeline edit mode?
My game includes image files and json configuration files that I would like to make accessible in the deployed game's folder structure so that players can easily edit or swap them out.
I have considered/tried the following approaches:
My initial approach was to use the Resources folder and code
such as Resources.Load<TextAsset>("Rules.json"). Of course,
this did not work as the resources folder is compiled during builds.
I investigated the Addressables and AssetBundle features, but they do not seem aimed at solving this problem.
After asking around, I went for using .NET's own file methods, going
for code like File.ReadAllText(Application.dataPath + Rules.json). This seems like it will work, but such files are still not deployed automatically and would have to manually be copied over.
It seems that the StreamingAssets folder exists for this, since the manual advertises that its contents are copied verbatim on the target machine. I assume that its contents should be read as in the previous point, with non-Unity IO calls like File.ReadAllText(Application.streamingAssetsPath + Rules.json)?
So yeah, what is the 'canonical' approach for this? And with that approach, is it still possible to get the affected files as assets (e.g. something similar to Resources.Load<Sprite>(path)), or is it necessary to use .NET IO methods to read the files and then manually turn them into Unity objects?
After asking the same question on the Unity forums, I was advised to use the StreamingAssets folder and told that it is necessary to use .NET IO methods with it.
An example for how to load sprites as files using standard IO can be seen here: https://forum.unity.com/threads/generating-sprites-dynamically-from-png-or-jpeg-files-in-c.343735/
static public Sprite LoadSpriteFromFile(
string filename,
float PixelsPerUnit = 100.0f,
SpriteMeshType type = SpriteMeshType.FullRect)
{
// Load a PNG or JPG image from disk to a Texture2D, assign this texture to a new sprite and return its reference
Texture2D SpriteTexture = LoadTexture(filename);
Sprite NewSprite = Sprite.Create(
SpriteTexture,
new Rect(0,
0,
SpriteTexture.width,
SpriteTexture.height),
new Vector2(0, 0),
PixelsPerUnit,
0,
type);
return NewSprite;
}
static private Texture2D LoadTexture(string FilePath)
{
// Load a PNG or JPG file from disk to a Texture2D
// Returns null if load fails
Texture2D Tex2D;
byte[] FileData;
if (File.Exists(FilePath))
{
FileData = File.ReadAllBytes(FilePath);
Tex2D = new Texture2D(2, 2);
// If the image is blurrier than what you get with a manual Unity import, try tweaking these two lines:
Tex2D.wrapMode = TextureWrapMode.Clamp;
Tex2d.filterMode = FilterMode.Bilinear;
// Load the imagedata into the texture (size is set automatically)
if (Tex2D.LoadImage(FileData))
{
return Tex2D; // If data = readable -> return texture
}
}
return null;
}
I am using the A* graph package that I found here.
https://arongranberg.com/astar/download
it all works well in scene view and I was able to set the graph to treat walls like obstacles.
However once I start the game the canvas scales and the graph's nodes no longer align with the walls.
this is really messing up my path finding. If anyone has any ideas how to fix this that would be much appreciated. I tried parenting the graph to the canvas but it still doesn't scale.
Kind regards
For anyone struggling with this what we did is edited the script of the A* code, in the update function we just got it to rescan once. This means that once the game started and all the scaling had taken place the graph re adjusted its bounds. This is probably not the most proper way but it only took four lines and worked for us.
private bool scanAgain = true;
private void Update () {
// This class uses the [ExecuteInEditMode] attribute
// So Update is called even when not playing
// Don't do anything when not in play mode
if (!Application.isPlaying) return;
navmeshUpdates.Update();
// Execute blocking actions such as graph updates
// when not scanning
if (!isScanning) {
PerformBlockingActions();
}
// Calculates paths when not using multithreading
pathProcessor.TickNonMultithreaded();
// Return calculated paths
pathReturnQueue.ReturnPaths(true);
if (scanAgain)
{
Scan();
scanAgain = false;
}
I got a problem in my project. I want to know that mouse cliked happend on GUI or on any game object.
I have tried this but it is showing null reference exception
EventSystem eventSystem = EventSystem.current;
if (eventSystem.IsPointerOverGameObject())
Debug.Log("left click over a gui element");
how to detect?? Is there any event available or else?
IsPointerOverGameObject() is fairly broken on mobile and some corner cases. We rolled our own for our project and it works like a champ on all platforms we've thrown it at.
private bool IsPointerOverUIObject() {
PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current);
eventDataCurrentPosition.position = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
return results.Count > 0;
}
Source:
http://forum.unity3d.com/threads/ispointerovereventsystemobject-always-returns-false-on-mobile.265372/
There is some approaches that you can use to detect if your mouse is over a legacy GUI element here I'll show you one that I hope will work fine for you, if not research a little about "mouse over GUI" and you'll find a lot of different ways to do it (this one is what I use on my legacy GUI projects and usually works fine with touch):
Create an easily accessible behaviour (usually a singleton) to hold your MouseOverGUI "status":
if you are using GUILayout.Button you need to catch the last drawn rect, if you are GUI.Button just use the same rect you passed as button's param like this:
// GUILayout
( Event.current.type == EventType.Repaint &&
GUILayoutUtility.GetLastRect().Contains( Event.current.mousePosition ) ) {
mouseOverGUI = true;
}
} // you need call it in your OnGUI right after the call to the element that you want to control
// GUI
( Event.current.isMouse &&
yourRect.Contains( Event.current.mousePosition ) ) {
mouseOverGUI = true;
}
}
after that you just need to test if your mouseOverGUI is true or false to allow or not your desired click actions before execute them. (a good understanding of unity loops will help you to catch and test the flag in correct timing to avoid problems expecting to get something that already changed)
edited: also remember to reset mouseOverGUI to false when it is not over GUI ;)
Finally got my answer here:
There are three ways to do this, as demonstrated in this video tutorial. this video save me:).
Use EventSystem.current.IsPointerOverGameObject
Convert your OnMouseXXX and Raycasts to an EventSystem trigger. Use a physics raycaster on the camera
Implement the various handler interfaces from the EventSystems namespace. Use a physics raycaster on the camera.
We have our asset bundles stored on an Amazon S3 bucket. When the game starts, it determines which bundles it needs to download new versions of, using WWW.LoadFromCacheOrDownload.
The problem we're running into is the memory iOS reports it has allocated for our app keeps increasing, however the memory Unity reports it's using (through the profiler) always stays the same. We have enough bundles that by the time it has finished downloading everything we need, it has invariably received a memory warning from iOS, and we are shutdown due to memory pressure shortly after.
Common solutions we have in place already: Unloading the assetbundle after the WWW is finished, using assetBundle.unload(), calling Resources.UnloadUnusedAssets(), and calling Dispose() on the WWW. None of it is solving the problem.
Code follows:
private IEnumerator DownloadBundle(DownloadQueueEntry entry, DownloadFinishedCallback callback)
{
while (!entry.finished)
{
// grab bundle off S3
string url = string.Format(BUNDLE_URL_FORMAT, entry.directory, entry.assetName);
WWW www = WWW.LoadFromCacheOrDownload(url, entry.version);
yield return www;
if (string.IsNullOrEmpty(www.error))
{
Debug.Log("[BundleDownloader] Download Completed " + entry.assetName);
entry.finished = true;
entry.downloading = false;
www.assetBundle.Unload (true);
Resources.UnloadUnusedAssets ();
}
else
{
// usually timed out resolving host, just try again for now
Debug.LogError("[BundleDownloader] Download failed: " + url + " Error: " + www.error);
}
www.Dispose();
www = null;
}
if(callback != null)
{
callback ();
}
}
--edit--
A screenshot showing the increasing memory usage is at the link below. Memory usage proceeds like that until it has chewed up around 150MB. This is all in an scene that only has a GameObject for init scripts in it (no art or anything).
https://www.dropbox.com/s/3b6skexz6xhug5g/Screenshot%202014-03-28%2014.54.26.png
As the Unity docs suggest, you should really be encapsulating your usage of the WWW object/caching routines in a "using" statement block.
using System;
using UnityEngine;
using System.Collections;
public class CachingLoadExample : MonoBehaviour {
public string BundleURL;
public string AssetName;
public int version;
void Start() {
StartCoroutine (DownloadAndCache());
}
IEnumerator DownloadAndCache (){
// Wait for the Caching system to be ready
while (!Caching.ready)
yield return null;
// Load the AssetBundle file from Cache if it exists with the same version or download and store it in the cache
using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
yield return www;
if (www.error != null)
throw new Exception("WWW download had an error:" + www.error);
AssetBundle bundle = www.assetBundle;
if (AssetName == "")
Instantiate(bundle.mainAsset);
else
Instantiate(bundle.Load(AssetName));
// Unload the AssetBundles compressed contents to conserve memory
bundle.Unload(false);
} // memory is freed from the web stream (www.Dispose() gets called implicitly)
}
}
"using" statements are a C# feature to ensure that "Dispose()" methods work correctly (and in many cases, are called automagically for you).
As stated in MS's docs: http://msdn.microsoft.com/en-us/library/yh598w02.aspx
"[using] Provides a convenient syntax that ensures the correct use of IDisposable objects."
I'm guess your memory leak is due to these functions not performing as intended (possibly due to improper configuration, not sure).
I had similar issues on AssetBundle downloading on iPad. However, in my case;
I was downloading assetbundle from server (without using cache) and loading it later. In one asset bundle, it boosts the memory if the asset bundle has images more than 100 or lets say 200.
This was not UnityDestroyWWWConnection or "using" issue. I was downloading 60MB asset bundle (with using best-compression on asset bundle generation) and it uses about 450MB while downloading.
I checked the result from instrument and saw tons of small malloc while downloading asset bundle. You can check the instrument screenshot from here.
My guess - not sure: Unity extract the information from asset bundle while downloading continues, which gives error on memory. It gets the header info first, and understands that it is unity3d object and because it downloads with www class it prepare for WWW asset bundle.
My solution was:
Compressing assetbundle and putting server as zip.
Downloading asset bundle zip and extracting on iPad (using SharpZipLib)
We ran into a similar issue in our app. We were loading lots of textures from WWW and noticing iOS being the only platform to have a memory leak from it. We eventually found our solution here http://forum.unity3d.com/threads/www-memory-leak-ios.227753/. Basically, there is a known issue in unity 4.3 that leaks the data from www calls. This SHOULD be fixed in unity 4.5. In the meantime, you can follow Alexey's suggestion to modify the code in the generate xcode project or update to 4.5:
4.5 will have the fix.
Essentially you need:
search for
extern "C" void UnityDestroyWWWConnection(void* connection)
in WWWConnection.mm
[delegate.connection cancel];
delegate.connection = nil;
[delegate.data release]; // <-- ADD THIS
[delegate release];