目次
-はじめに
-やったこと
-あとがき
はじめに
※この記事は名工大アドベントカレンダーに参加しています
以前私の記事でなんでもタワーバトルというゲームを作るためにUnityStandaloneFileBrowserを使って、ゲーム中に外部ファイルを参照してその画像を使ったタワーバトル系のゲームを作りました。その続きです。
↓作ったゲーム
↓以前書いた記事
この記事を書いてからしばらく離れていたのですが、一年ぶりに制作ファイルを開き、実現方法について調べてみたところ上記のやり方ではWebGLでのビルドができないと書いてありました(WebGL以外ならほぼ大丈夫)。そのため外部ファイルを参照する仕組みを一から作り直すことになりました。
(この先書いてあることはほとんどAIと一緒にやったことです)
Unityバージョン:6000.2.13f1
やったこと
簡単に言うとUnity内に新たなプラグインを作りました。
mergeInto(LibraryManager.library, {
OpenFileDialog: function () {
var input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.onchange = function (event) {
var files = event.target.files;
for (var i = 0; i < files.length; i++) {
var file = files[i];
if(file) {
var url = URL.createObjectURL(file);
// Unityへ渡す
SendMessage("BackGround", "OnFileSelected", url);
}
}
};
input.click();
},
RevokeObjectURL: function (strPtr) {
var url = UTF8ToString(strPtr);
console.log("Revoke URL : " + url);
URL.revokeObjectURL(url);
}
});
このJavaScriptのコードをAssets/Pluginsのフォルダに入れることによりWebGLでビルドしたゲーム(例えばunityroomに投稿したゲーム)に組み込むことで画像選択画面になります
このコードを呼び出すのに専用のスクリプトが必要です
using UnityEngine;
using System.Runtime.InteropServices;
public class CallPlugin : MonoBehaviour
{
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void OpenFileDialog();
#endif
public void OnButtonClick()
{
#if UNITY_WEBGL && !UNITY_EDITOR
ShowAlert();
LogMessage("Unityから送ったメッセージ(Button Clicked)");
#endif
}
public void OnButtonFile()
{
#if UNITY_WEBGL && !UNITY_EDITOR
OpenFileDialog();
#endif
}
}
このスクリプトを付与したボタンを押すと先ほどのスクリプトを呼び出し、ブラウザに応答画面を出すことができます。
↓参考にしたサイトです!
テストでプラグインが正常に動作してることが分かったので、次に画像ファイルを選択し写すところまで作ります。
プラグインで呼んだコードを受け取るスクリプト
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;
public class FileLoader : MonoBehaviour
{
[SerializeField] private List<RawImage> outputs;
private Queue<string> urlQueue = new Queue<string>();
private bool isLoading = false;
private int currentIndex = 0;
// private StoreImage storeImage;
private void Start()
{
// storeImage = GetComponent<StoreImage>();
}
public void OnFileSelected(string url)
{
urlQueue.Enqueue(url);
if (!isLoading)
StartCoroutine(ProcessQueue());
}
private IEnumerator ProcessQueue()
{
isLoading = true;
while (urlQueue.Count > 0)
yield return LoadFile(urlQueue.Dequeue());
isLoading = false;
}
private IEnumerator LoadFile(string url)
{
using (UnityWebRequest www = UnityWebRequest.Get(url))
{
www.downloadHandler = new DownloadHandlerBuffer();
yield return www.SendWebRequest();
if (www.result == UnityWebRequest.Result.Success)
{
byte[] bytes = www.downloadHandler.data;
// ここがポイント:バイトから直接デコード
Texture2D tex = new Texture2D(2, 2, TextureFormat.RGBA32, false);
bool ok = tex.LoadImage(bytes, markNonReadable: false); // readable を維持
if (!ok)
{
Debug.LogError("画像デコードに失敗: " + url);
yield break;
}
// 必要ならあとでリサイズを戻す。今は安全最小で表示のみ
if (currentIndex < outputs.Count)
{
outputs[currentIndex].texture = tex;
currentIndex++;
}
else
{
Debug.LogWarning("表示可能な画像数を超えました。");
}
// storeImage.StoreTexture(tex);
if(StoreImage.Instance != null)
{
StoreImage.Instance.StoreTexture(tex);
}
else
{
Debug.LogError("StoreImage インスタンスが見つかりません。");
}
}
else
{
Debug.LogError("読み込み失敗: " + www.error);
}
}
}
}
スクリプトの説明をするとボタンを押した時にCallPluginでプラグインを呼び、プラグインからファイルを開き画像を選択、Unity内で画像を読み込むことができたらFileLoaderでTexture2DとしてUIのRawImageに表示することができます。
画面ではRawImageの比率が1対1になっているため画像の比率が変わることもありますが変化させることもできると思います。

ちなみに画像を複数選択して同時に読み込むことができます(RawImageの数だけ)
multipleをtrueにしてQueue型を使って順番にTexture変化させていくようにしています。Queueを使って順番を整えているのは一気に画像を変化させようとするとブラウザの動作が重くなり、落ちる可能性があるからです。一枚づつ処理させることで落ちることを防いでいます。
これで複数枚の画像を読み込むことに成功したので最後に取得したTextureをSpriteに変えてオブジェクトのSpriteRendererに表示するところまでやります。StoreImageは既にインスタン化しておりあらゆるスクリプトから値を取得できる状況となっているので始めにSpriteを作らせてから取得します(本当はTextureを受け取ったタイミングでSpriteとして保存したかったのですが、同時に行うとどうしても処理落ちしてしまうためタイミングをずらしました)。
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public class StoreImage : MonoBehaviour
{
public static StoreImage Instance { get; private set; }
private List<Texture2D> textures = new List<Texture2D>();
[SerializeField] private int maxSize = 256;
private void Awake()
{
if(Instance != null && Instance != this)
{
Destroy(this.gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(this.gameObject);
}
public void StoreTexture(Texture2D tex)
{
Texture2D resized = ResizeTexture(tex, maxSize);
textures.Add(tex);
Debug.Log("StoreImage: テクスチャを保存しました。現在のテクスチャ数: " + textures.Count);
}
public List<Texture2D> GetTextures()
{
return textures;
}
public List<Sprite> GetSprites()
{
List<Sprite> sprites = new List<Sprite>();
foreach (var tex in textures)
{
sprites.Add(Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f)));
}
return sprites;
}
private Texture2D ResizeTexture(Texture2D source, int maxSize)
{
int width = source.width;
int height = source.height;
if (width <= maxSize && height <= maxSize)
return source; // リサイズ不要
float scale = Mathf.Min((float)maxSize / width, (float)maxSize / height);
int newWidth = Mathf.RoundToInt(width * scale);
int newHeight = Mathf.RoundToInt(height * scale);
RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight);
Graphics.Blit(source, rt);
RenderTexture.active = rt;
Texture2D result = new Texture2D(newWidth, newHeight, source.format, false);
result.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
result.Apply();
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(rt);
return result;
}
}
あとはGameManagerの方にGetSprites()を呼び出す関数を書けばオブジェクトにSprite型で画像を貼ることができます。
あとがき
このプラグインはほとんどAIに考えてもらっており、Unity&プログラミング歴2年の私には何が起こっているのか完全な理解はできてないですが、ほぼすべて問題なく動作しました。AIスゲー
