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
Related
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;
}
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.
I am trying to load some sounds in OGG format into my game at runtime in a WebGL build. I use a WWW class to fetch the file which has an ".ogg" extension and then I call www.audioClip to get the downloaded file. This works on other platforms but fails in WebGL.
Unity throws up this error message: "Streaming of 'ogg' on this platform is not supported". Strange since I am not trying to stream it, and I have tried explicitly calling GetAudioClip(false, false, AudioType.OGGVORBIS) and got the same result.
I have tried converting my OGG file to AAC (with M4A and MP4 extensions) and loading this with www.audioClip (error that it cannot determine the file type from the URL) and www.GetAudioClip(false, false, AudioType.MPEG) (no error but also no sound). The closest thing to a solution I've seen online is to use MP3 instead but I don't want to do this for licensing reasons.
Is WebGL in Unity restricted to audio assets that are build into the application?
try:
WWW data = new WWW (url); yield return data;
AudioClip ac = data.GetAudioClipCompressed(false, AudioType.AUDIOQUEUE) as AudioClip;
if(ac != null)
{
ac.name = "mySoundFile.ogg";
gameObject.GetComponent<AudioSource> ().clip = ac;
}
else
{
gameObject.GetComponent<AudioSource> ().clip = null;
Debug.Log("no audio found.");
}
works for me with .ogg files.
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.
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.