Manage prefabs programmatically - unity3d

Is there a way to find all prefabs defined/available in a Unity project programmatically?
This should include prefabs sitting in the Assets folder which are not in the Resources folder.
Use-cases range from dynamically resolving dependencies (from a name for example) to wanting to implement some central instantiation code.

string path = Application.dataFolder;
string[] files = System.IO.Directory.GetFiles(path, "*.prefab");
for(int i=0;i<files.Length;i++)
Debug.Log(files[i];
If you wish to load the prefab into the scene:
var url = "file://....prefab";
var www = new WWW (url);
yield return www;
www.bytes[]; // This way, you have to convert it to game object yourself.

Related

How to retrieve AssetReference from Scene?

I have a Scene marked as Addressable. Given the Scene itself, is it possible to get the AssetReference for that scene?
For example, in the following code snippet, what would ?? need to be?
Scene activeScene = SceneManager.GetActiveScene();
// What can I call here to get the AssetReference?
AssetReference activeSceneReference = ??;
If this is possible at all I honetsly can't tell since I never worked with Addressables so far.
However, following the APIs this might be possible but requires multiple steps:
Since the activeScene is a loaded scene and the same scene can be loaded multiple times via additive loading it is not actually an asset anymore and therefore doesn't have an asset reference at all.
So the first step is actually
Get the asset for a loaded scene
Having your loaded scene you can still get its original asset path via Scene.path.
var activeScene = SceneManager.GetActiveScene();
var activeScenePath = activeScene.path;
And then use AssetDatabase.LoadAssetAtPath to load the original asset
var activeSceneAsset = AssetDataBase.LoadAssetAtPath<Scene>(activeScenePath);
Now that we have the actual asset of the loaded scene we can
Get the GUID of an Asset
Having already the asset activeSceneAsset you can use AssetDatabase.TryGetGUIDAndLocalFileIdentifier in order to optain the GUID we need in the last step:
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(activeSceneAsset, out var guid, out var file))
{
// TODO see below
}
and now having that guid we can use it in order to
Get the AssetReference via the GUID
The reason why we wanted to get the GUID is because we can now use it for the AssetReference constructor taking the GUID as parameter:
var activeSceneReference = new AssetReference(guid);
NOTE: All this is ofcourse only possible in the Unity Editor itself since AssetDataBase does not exist outside of it.

How can I make assets accessible to players for modding?

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;
}

Unity3D: Resource.Load defined AudioClip ends up Null going into the AudioSource

What I'm trying to do is contain an audio file in a folder (under Resources) where I can drop any qualifying audio file in the specified folder and have the numerous triggers in my program read from that single point (which is why my AudioClip below is public static so I can reference it). Currently, the same audio file works throughout the program, but to change the file requires manual redefining in the Inspector which my eventual client won't have access to, and besides is tedious due to the numerous reference points that exist.
Here's what I have so far:
public static AudioClip BGM;
public AudioSource BGMSource;
private string formatted1;
void Start()
{
foreach(string file in System.IO.Directory.GetFiles(Application.dataPath+"/Resources/Audio/BGM"))
{
if(file.EndsWith(System.IO.Patch.GetExtension(".mp3")))
{
formatted1 = file.Replace(".mp3",string.Empty);
BGM = Resources.Load<AudioClip>(formatted1);
//BGM = (AudioClip)Resources.Load(formatted1,typeof(AudioClip)); <--same result with this
Debug.Log("found: "+formatted1);
}
}
if(BGM == null)
{
Debug.Log("Yeah, its null");
}
BGMSource.PlayOneShot(BGM, .9f);
if(BGMSource.isPlaying != true)
{
Debug.Log("I'm not playing");
}
}
So as is, this just doesn't play, no error messages. Turns out BGM is null. The Debug says as so, but if I were to add a Debug call for BGMSource.clip.name, it will fully error out with a NullReferenceException on that Debug.
The Debug for the formatted1 string (File path and name), it does present the correct file called Test.mp3 ("C:/...Resources/Audio/BGM\Test") formatted without the ".mp3" as was recommended from another site. I did try with the .mp3 extension on, didn't seem to matter, still didn't play. I also tried with a .wav file and .ogg file, same result (note: all files were fine if I attached as a public AudioClip manually as also the AudioSource as written above would play in that case, but as I lead with, we don't want that for this case). Yes, all test audio files were in the directory /Resources/Audio/BGM.
Another site said something about adding to the top of the file [RequireComponent(typeof(AudioClip))] or [RequireComponent(typeof(AudioSource))]but that did nothing.
Lastly, this program will eventually be given to a group that won't have source access so they MUST be able to swap the audio file by dropping any .mp3 in Resources/Audio/BGM for auto play.
Any help is welcome, thanks!
First a general note: Never use + "/" for system file paths! Rather sue Path.Combine which automatically inserts the correct path separators according to the platform it is running on
string file in System.IO.Directory.GetFiles(Path.Combine(Application.dataPath, "Resources", "Audio", "BGM"))
Then please read the documentation of Resources.Load!
It requires your file(s) being placed inside a folder called Resources which is compiled into the application build and therefore can not be changed afterwards. This seems to be the case for you.
It does not take a full system path like you pass in since Directory.GetFiles returns
An array of the full names (including paths) for the files in the specified directory
but rather expects a path within all Resources folders - yes you can have multiple ones.
Let's say e.g. you put your files in a structure like
Assets
|--Resources
| |--someFile.mp3
|
|--SomeOtherFolder
| |--Resources
| | |--someOtherFile.mp3
| | |--ASubFolder
| | | |--yetAnotherFile.mp3
Then you would address these by using
Resources.Load<AudioClip>("someFile");
Resources.Load<AudioClip>("someOtherFile");
Resources.Load<AudioClip>("ASubfolder/yetAnotherFile");
since when build all Resources are packed together.
So in your case it should be
Resources.Load<AudioClip>("Audio/BGM/" + formatted1);
where you have to make sure that formatted1 is not a full path but only the filename! You can simply use Path.GetFileNameWithoutExtension so you don't even need your replace
var formatted1 = Path.GetFileNameWithoutExtension(file);
It is bad ^^
In their Best Practices for the Resources folder Unity themselves recommend
Don't use it!
However
Since it is not recommneded to use the Resources at all I would rather recommend:
If you don't want to change them later
You can't change the Resources afterwards for e.g. replacing a file. So if you can't change the files later anyway, then why not rather directly reference them in the places where they are needed later?
Simply put your audio files in a folder that is not Resources and reference them in your scripts directly where you need them:
// Simply drag&drop the clip into this field via the Inspector in Unity
[SerializeField] private AudioClip someClip;
If you want to change them later
In case you actually would like to be able to replace them later also after a build you could instead use UnityWebRequestMultimedia.GetAudioClip which can also be used to load files from a system file on runtime. For this you wouldn't put your files into the Resources or any other folder but rather either the StreamingAssets or the Application.persistentDataPath.
I usually go:
In the editor use StreamingAssets folder so all stuff lies inside the project and access it via Application.streamingAssetsPath
In a build first check if file exists in Application.persistentDataPath
If not copy it from Application.streamingAssetsPath and store it into Application.persistentDataPath
otherwise simply load it from Application.persistentDataPath
Modified API Example
[RequireComponent(typeof(AudioSource))]
public class AudioExample : MonoBehaviour
{
[SerializeField] private AudioSource _audioSource;
public List<AudioClip> LoadedAudioClips = new List<AudioClip>;
private List<UnityWebRequest> _runningWebRequests = new List<UnityWebRequest>();
private void Awake()
{
if(!_audioSource) _audioSource = GetComponent<AudioSource>();
}
private void Start()
{
StartCoroutine(GetAudioClip());
}
private IEnumerator GetAudioClip()
{
foreach(string file in System.IO.Directory.GetFiles(Path.Combine(Application.persistentDataPath, "Audio", "BGM"))
{
if(!file.EndsWith(System.IO.Patch.GetExtension(".mp3"))) continue;
UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip("file:///" + file, AudioType.MPEG);
{
_runningWebRequests.Add(www);
www.Send();
}
}
while(_runningWebRequests.Count > 0)
{
foreach(var www in _runningWebRequests.Where(www => www.isDone))
{
_runningWebRequests.Remove(www);
if (www.isError)
{
Debug.LogWarning(www.error);
}
else
{
var clip = DownloadHandlerAudioClip.GetContent(www);
if(clip == null)
{
Debug.LogError("Huh?!");
}
else
{
LoadedAudioClips.Add(clip);
}
}
}
yield return null;
}
}
}
Also saw you comments so:
StreamingAssets is also a special folder you can store files in you want to read in on runtime. It is local. Also this folder is not "visible" from the outside and can not be altered later.
If you need to be able to alter stuff later (like e.g. also saving files) you will always need to use the Application.persistentDataPath instead.

Unity3D trying to load a texture resource after build

I have a material that is using a texture (image) located in the Resources directory under Assets. Using Resources.load works while in the editor. The texture also loads properly on a build, but I would like to replace that image after the build by placing a different image (with the same name) in the built Resources directory.
On Windows, I think that directory is buildname_Data>Resources and on Mac I'm thinking it's Contents>Resources (after opening Package Contents). This works for a text file I'm using to load some data at startup, but the process is a bit different there as I'm not using Resources.load in that case.
The problem I'm having is that just placing a new image in the (i think) proper location does not override the image that the app was built with. I'm still seeing the original image. I've been scratching my head over this for the past couple days, and the Documentation (as well as various google searches) have not yielded insight into a solution (although it's likely staring me right in the face).
var MyTexture : Texture = Resources.Load("colorPatch");
var wbpLineRenderer : LineRenderer = someGameObject.AddComponent(LineRenderer);
wbpLineRenderer.material = new Material (Shader.Find("Particles/Alpha Blended"));
wbpLineRenderer.material = Resources.Load("curveLine") as Material;
wbpLineRenderer.material.mainTexture = MyTexture;
curveLine is a material in the Assets/Resources directory of the Editor.
colorPatch is an image file named colorPatch.png in the Assets/Resources directory of the Editor
Can someone please screw my head on straight about this? Can I actually swap an image used on a texture after build?
After a good breakfast and a bit more googling I have (re)discovered the solution, detailed here.
my implementation is as follows:
var textureURL : String = "file://" + Application.dataPath + "/Resources/colorPatch.png";
var www : WWW = new WWW(textureURL);
var tempTexture : Texture2D = new Texture2D(2, 2); //doesn't have to be the actual size
www.LoadImageIntoTexture(tempTexture); //image loads at 100% not 2x2 specified above
var wbpLineRenderer : LineRenderer = wellBorePath.AddComponent(LineRenderer);
wbpLineRenderer.material = new Material (Shader.Find("Particles/Alpha Blended"));
wbpLineRenderer.material = Resources.Load("curveLine") as Material;
wbpLineRenderer.material.mainTexture = tempTexture;
Once the app is built, place an image named colorPatch.png in the location:
Windows - appname_Data/Resources
Mac - Contents/Resources (after opening Package Contents).
Switching out the image colorPatch.png with another image (but still titled colorPatch.png) then launching the app, displays the new image as the texture.

About unity 3d load assetbundle difference

Under normal circumstances, I load assetbundle like this
WWW www = WWW.LoadFromCacheOrDownload("http://x.x.x.x/player.unity3d", 3);
yield return www;
but I want to load assetbundle by difference file, for example
I have a difference file like: http://x.x.x.x/player.unity3d.diff
I generate the diff by bsdiff (daemonology.net/bsdiff)
My question is; how can I load assetbundle by player.unity3d.diff?
I am trying to google it, but I not found anything.
Unity doesn't support loading bsdiff'ed files, but you could implement it yourself. Example:
// Load asset bundle
WWW www1 = WWW.LoadFromCacheOrDownload("original.bundle");
// Load diff
WWW www2 = WWW.LoadFromCacheOrDownload("updated.bundle.diff");
// Get bytes for both assets
byte[] original = www1.bytes;
byte[] diff = www2.bytes;
// Apply diff
byte[] updated = ApplyBspatch(original, diff);
// You can save updated bundle at this point to a file.
// Finally, create asset bundle
AssetBundle bundle = AssetBundle.CreateFromMemory(updated);
Here's one of bsdiff implementations in C# you could use to do the patching: https://github.com/LogosBible/bsdiff.net