Unityで画像を読み込む方法と実用的な活用例

unity

Unityでゲームやアプリを開発していると、外部の画像を読み込みたい場面が必ず出てきます。

例えば、以下のような場面です:

  • ユーザーのプロフィール画像を表示したい
  • ダウンロードコンテンツの画像を動的に読み込みたい
  • ゲーム内で撮影したスクリーンショットを表示したい
  • サーバーから最新のイベント画像を取得したい

しかし、画像の読み込みは意外と奥が深く、間違った方法で実装するとアプリがフリーズしたり、メモリ不足になったりすることがあります。

この記事では、Unityでの画像読み込みの基本的な方法から、注意点、実用的な応用例までを、初心者にもわかりやすく詳しく解説します。

スポンサーリンク
  1. 画像読み込みの基本とは?
    1. Unityでの画像の扱い方
    2. 画像読み込みの主な方法
  2. Resourcesフォルダを使った基本的な読み込み
    1. Resourcesフォルダとは?
    2. フォルダの準備
    3. 基本的な読み込みコード
    4. コードの詳しい説明
    5. Resourcesフォルダのメリット・デメリット
  3. StreamingAssetsや外部画像の読み込み
    1. StreamingAssetsフォルダとは?
    2. StreamingAssetsの準備
    3. StreamingAssetsからの読み込みコード
    4. 外部URLからの画像読み込み
    5. 進歩的な読み込み(プログレスバー付き)
  4. 画像読み込み時の重要な注意点
    1. ファイル形式の制限と推奨事項
    2. 非同期処理の必要性
    3. メモリ管理の重要性
    4. セキュリティの配慮
  5. 読み込んだ画像の実用的な活用例
    1. ユーザーアバターシステム
    2. 動的コンテンツ表示システム
    3. スクリーンショット機能
  6. 高度な画像処理テクニック
    1. 画像のリサイズ処理
    2. キャッシュシステム
    3. 使用例:キャッシュを活用した画像表示
  7. パフォーマンス最適化のコツ
    1. テクスチャ圧縮の活用
    2. 非同期読み込みの最適化
  8. デバッグとログ機能
    1. 詳細なログシステム
    2. パフォーマンス測定
  9. よくある問題とその解決方法
    1. 問題1:メモリリークの発生
    2. 問題2:ネットワークエラーの処理
    3. 問題3:異なるプラットフォームでの動作差異
  10. まとめ:Unity画像読み込みのベストプラクティス
    1. 基本的な読み込み方法の使い分け
    2. パフォーマンス最適化の要点
    3. 実用的な活用シーン

画像読み込みの基本とは?

Unityでの画像の扱い方

Unityでは、画像(テクスチャ)を扱うためにTexture2Dというクラスを使用します。

これは、2次元の画像データを表現するためのUnity専用のクラスです。

Texture2Dでできること

  • 画像ファイルからテクスチャを作成
  • ピクセル単位での画像操作
  • UIコンポーネントやマテリアルへの適用
  • メモリ効率的な画像管理

画像読み込みの主な方法

Unityで画像を読み込む方法は大きく分けて3つあります:

1. Resourcesフォルダからの読み込み

  • プロジェクト内に画像を埋め込む方法
  • ビルド時に一緒にパッケージされる
  • 簡単だが柔軟性は低い

2. StreamingAssetsからの読み込み

  • 実行ファイルと一緒に配布される画像を読み込む
  • ファイルパスでアクセス可能
  • ビルド後でも画像の差し替えが可能

3. 外部URLからの読み込み

  • インターネット上の画像を読み込む
  • 最新の画像を動的に取得可能
  • 通信が必要なため注意が必要

Resourcesフォルダを使った基本的な読み込み

Resourcesフォルダとは?

Resourcesフォルダは、Unityプロジェクトの特別なフォルダです。

ここに入れたファイルは、ビルド時に実行ファイルに埋め込まれ、プログラムから簡単にアクセスできます。

フォルダの準備

  1. Assetsフォルダ内に**「Resources」**という名前のフォルダを作成
  2. その中に画像ファイル(PNG、JPG など)を配置
  3. 例:Assets/Resources/Images/player_avatar.png

基本的な読み込みコード

using UnityEngine;
using UnityEngine.UI;

public class ImageLoader : MonoBehaviour 
{
    [SerializeField] private RawImage displayImage;  // 表示用のRawImage
    
    void Start() 
    {
        LoadImageFromResources();
    }
    
    void LoadImageFromResources() 
    {
        // Resourcesフォルダから画像を読み込み
        Texture2D loadedTexture = Resources.Load<Texture2D>("Images/player_avatar");
        
        if (loadedTexture != null) 
        {
            // 読み込み成功:RawImageに画像を設定
            displayImage.texture = loadedTexture;
            Debug.Log("画像の読み込みに成功しました");
        } 
        else 
        {
            // 読み込み失敗:エラーログを出力
            Debug.LogError("画像が見つかりませんでした");
        }
    }
}

コードの詳しい説明

Resources.Load<Texture2D>()の使い方

  • Resources.Load<Texture2D>("パス")で画像を読み込み
  • パスは Resourcesフォルダ以下の相対パス
  • ファイル拡張子(.png、.jpg)は不要
  • 戻り値は Texture2D型(読み込み失敗時は null)

エラーハンドリングの重要性

  • 画像が存在しない場合の対処
  • null チェックによる安全な処理
  • デバッグログによる問題の特定

Resourcesフォルダのメリット・デメリット

メリット

  • 実装が簡単
  • ビルド時に自動的に含まれる
  • 追加の設定が不要

デメリット

  • ビルド後の画像変更ができない
  • メモリ使用量が増加する可能性
  • 大量の画像には向かない

StreamingAssetsや外部画像の読み込み

StreamingAssetsフォルダとは?

StreamingAssetsフォルダは、実行ファイルと一緒に配布されるファイルを格納する特別なフォルダです。ビルド後でもファイルの差し替えが可能で、より柔軟な画像管理ができます。

StreamingAssetsの準備

  1. Assetsフォルダ内に**「StreamingAssets」**という名前のフォルダを作成
  2. その中に画像ファイルを配置
  3. 例:Assets/StreamingAssets/DynamicImages/event_banner.png

StreamingAssetsからの読み込みコード

using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.UI;

public class StreamingAssetsImageLoader : MonoBehaviour 
{
    [SerializeField] private RawImage displayImage;
    
    void Start() 
    {
        StartCoroutine(LoadImageFromStreamingAssets("DynamicImages/event_banner.png"));
    }
    
    IEnumerator LoadImageFromStreamingAssets(string fileName) 
    {
        // StreamingAssetsのパスを取得
        string filePath = Path.Combine(Application.streamingAssetsPath, fileName);
        
        // プラットフォーム別のパス処理
        if (filePath.Contains("://")) 
        {
            // Android等でのファイルアクセス
            UnityWebRequest request = UnityWebRequest.Get(filePath);
            yield return request.SendWebRequest();
            
            if (request.result == UnityWebRequest.Result.Success) 
            {
                byte[] imageData = request.downloadHandler.data;
                Texture2D texture = new Texture2D(2, 2);
                
                if (texture.LoadImage(imageData)) 
                {
                    displayImage.texture = texture;
                    Debug.Log("StreamingAssets から画像を読み込みました");
                }
            }
            else 
            {
                Debug.LogError($"ファイルの読み込みに失敗: {request.error}");
            }
        } 
        else 
        {
            // PC等での直接ファイルアクセス
            if (File.Exists(filePath)) 
            {
                byte[] imageData = File.ReadAllBytes(filePath);
                Texture2D texture = new Texture2D(2, 2);
                
                if (texture.LoadImage(imageData)) 
                {
                    displayImage.texture = texture;
                    Debug.Log("StreamingAssets から画像を読み込みました");
                }
            } 
            else 
            {
                Debug.LogError($"ファイルが見つかりません: {filePath}");
            }
        }
    }
}

外部URLからの画像読み込み

インターネット上の画像を読み込む方法です。最新の画像を動的に取得できるため、イベント画像やユーザーのプロフィール画像などに活用できます。

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class WebImageLoader : MonoBehaviour 
{
    [SerializeField] private RawImage displayImage;
    [SerializeField] private string imageURL = "https://example.com/image.jpg";
    
    void Start() 
    {
        StartCoroutine(LoadImageFromURL(imageURL));
    }
    
    IEnumerator LoadImageFromURL(string url) 
    {
        // URL の妥当性チェック
        if (string.IsNullOrEmpty(url)) 
        {
            Debug.LogError("URLが指定されていません");
            yield break;
        }
        
        Debug.Log($"画像を読み込み中: {url}");
        
        // UnityWebRequestTexture を使用して画像を取得
        UnityWebRequest request = UnityWebRequestTexture.GetTexture(url);
        
        // タイムアウト設定(10秒)
        request.timeout = 10;
        
        yield return request.SendWebRequest();
        
        // 結果の確認
        if (request.result == UnityWebRequest.Result.Success) 
        {
            Texture2D texture = DownloadHandlerTexture.GetContent(request);
            
            if (texture != null) 
            {
                displayImage.texture = texture;
                Debug.Log("外部画像の読み込みに成功しました");
            } 
            else 
            {
                Debug.LogError("テクスチャの作成に失敗しました");
            }
        } 
        else 
        {
            Debug.LogError($"画像の読み込みに失敗: {request.error}");
            
            // エラーの種類に応じた処理
            switch (request.result) 
            {
                case UnityWebRequest.Result.ConnectionError:
                    Debug.LogError("ネットワーク接続エラー");
                    break;
                case UnityWebRequest.Result.ProtocolError:
                    Debug.LogError($"HTTPエラー: {request.responseCode}");
                    break;
                case UnityWebRequest.Result.DataProcessingError:
                    Debug.LogError("データ処理エラー");
                    break;
            }
        }
        
        // リソースの解放
        request.Dispose();
    }
}

進歩的な読み込み(プログレスバー付き)

大きな画像をダウンロードする際に、プログレスバーを表示する方法です。

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class ProgressiveImageLoader : MonoBehaviour 
{
    [SerializeField] private RawImage displayImage;
    [SerializeField] private Slider progressBar;
    [SerializeField] private Text statusText;
    
    IEnumerator LoadImageWithProgress(string url) 
    {
        statusText.text = "画像をダウンロード中...";
        progressBar.value = 0f;
        
        UnityWebRequest request = UnityWebRequestTexture.GetTexture(url);
        
        // 非同期でリクエストを開始
        UnityWebRequestAsyncOperation operation = request.SendWebRequest();
        
        // ダウンロード進捗を更新
        while (!operation.isDone) 
        {
            progressBar.value = request.downloadProgress;
            statusText.text = $"ダウンロード中... {(request.downloadProgress * 100):F1}%";
            yield return null;
        }
        
        if (request.result == UnityWebRequest.Result.Success) 
        {
            Texture2D texture = DownloadHandlerTexture.GetContent(request);
            displayImage.texture = texture;
            statusText.text = "読み込み完了";
            progressBar.value = 1f;
        } 
        else 
        {
            statusText.text = "読み込み失敗";
            Debug.LogError(request.error);
        }
        
        request.Dispose();
    }
}

画像読み込み時の重要な注意点

ファイル形式の制限と推奨事項

対応形式

  • PNG:透明度サポート、高品質、ファイルサイズ大
  • JPG:ファイルサイズ小、透明度なし、写真に適している
  • TGA:高品質、開発用途
  • BMP:非圧縮、ファイルサイズ大

推奨設定

// テクスチャの設定例
texture.filterMode = FilterMode.Bilinear;  // 滑らかな表示
texture.wrapMode = TextureWrapMode.Clamp;  // エッジの処理

非同期処理の必要性

画像の読み込みは時間がかかる処理です。特に外部URLからの読み込みでは、必ず非同期処理を使用しましょう。

同期処理の問題点

  • メインスレッドをブロックしてアプリがフリーズ
  • ユーザー体験の悪化
  • ANR(Application Not Responding)エラー

非同期処理の利点

  • UIの応答性を維持
  • 複数の画像を並行して読み込み可能
  • エラーハンドリングの柔軟性

メモリ管理の重要性

画像は大量のメモリを消費します。適切な管理をしないとメモリ不足になる可能性があります。

public class ImageMemoryManager : MonoBehaviour 
{
    private Texture2D currentTexture;
    
    void LoadNewImage(Texture2D newTexture) 
    {
        // 古いテクスチャを解放
        if (currentTexture != null) 
        {
            DestroyImmediate(currentTexture);
            currentTexture = null;
        }
        
        // 新しいテクスチャを設定
        currentTexture = newTexture;
        displayImage.texture = currentTexture;
    }
    
    void OnDestroy() 
    {
        // オブジェクト破棄時にテクスチャも解放
        if (currentTexture != null) 
        {
            DestroyImmediate(currentTexture);
        }
    }
}

セキュリティの配慮

外部URLから画像を読み込む際は、セキュリティに十分注意する必要があります。

public class SecureImageLoader : MonoBehaviour 
{
    // 許可されたドメインのリスト
    private readonly string[] allowedDomains = {
        "https://cdn.example.com",
        "https://images.gameserver.com"
    };
    
    bool IsURLAllowed(string url) 
    {
        if (string.IsNullOrEmpty(url)) 
            return false;
            
        // HTTPSのみ許可
        if (!url.StartsWith("https://")) 
        {
            Debug.LogWarning("HTTPSでないURLは許可されていません");
            return false;
        }
        
        // 許可されたドメインかチェック
        foreach (string domain in allowedDomains) 
        {
            if (url.StartsWith(domain)) 
            {
                return true;
            }
        }
        
        Debug.LogWarning($"許可されていないドメイン: {url}");
        return false;
    }
    
    IEnumerator LoadSecureImage(string url) 
    {
        if (!IsURLAllowed(url)) 
        {
            Debug.LogError("URLが許可されていません");
            yield break;
        }
        
        // 通常の読み込み処理
        UnityWebRequest request = UnityWebRequestTexture.GetTexture(url);
        yield return request.SendWebRequest();
        
        // 以下、通常の処理...
    }
}

読み込んだ画像の実用的な活用例

ユーザーアバターシステム

プレイヤーがカスタムアバターを設定できるシステムの実装例です。

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class AvatarSystem : MonoBehaviour 
{
    [SerializeField] private RawImage avatarDisplay;
    [SerializeField] private Button[] presetAvatars;
    [SerializeField] private Button customAvatarButton;
    
    // プリセットアバターのパス
    private readonly string[] presetPaths = {
        "Avatars/avatar_01",
        "Avatars/avatar_02",
        "Avatars/avatar_03"
    };
    
    void Start() 
    {
        SetupPresetButtons();
        SetupCustomButton();
        LoadSavedAvatar();
    }
    
    void SetupPresetButtons() 
    {
        for (int i = 0; i < presetAvatars.Length; i++) 
        {
            int index = i; // ラムダ式でのキャプチャ対策
            presetAvatars[i].onClick.AddListener(() => LoadPresetAvatar(index));
        }
    }
    
    void SetupCustomButton() 
    {
        customAvatarButton.onClick.AddListener(LoadCustomAvatar);
    }
    
    void LoadPresetAvatar(int index) 
    {
        if (index < 0 || index >= presetPaths.Length) 
            return;
            
        Texture2D avatarTexture = Resources.Load<Texture2D>(presetPaths[index]);
        
        if (avatarTexture != null) 
        {
            avatarDisplay.texture = avatarTexture;
            SaveAvatarChoice("preset", index.ToString());
        }
    }
    
    void LoadCustomAvatar() 
    {
        // カスタムアバターのURL入力ダイアログを表示
        ShowCustomAvatarDialog();
    }
    
    void ShowCustomAvatarDialog() 
    {
        // 簡単な入力ダイアログの実装例
        string customURL = ""; // 実際はInputFieldから取得
        
        if (!string.IsNullOrEmpty(customURL)) 
        {
            StartCoroutine(LoadCustomAvatarFromURL(customURL));
        }
    }
    
    IEnumerator LoadCustomAvatarFromURL(string url) 
    {
        UnityWebRequest request = UnityWebRequestTexture.GetTexture(url);
        yield return request.SendWebRequest();
        
        if (request.result == UnityWebRequest.Result.Success) 
        {
            Texture2D customTexture = DownloadHandlerTexture.GetContent(request);
            avatarDisplay.texture = customTexture;
            SaveAvatarChoice("custom", url);
        }
        
        request.Dispose();
    }
    
    void SaveAvatarChoice(string type, string value) 
    {
        PlayerPrefs.SetString("AvatarType", type);
        PlayerPrefs.SetString("AvatarValue", value);
        PlayerPrefs.Save();
    }
    
    void LoadSavedAvatar() 
    {
        string avatarType = PlayerPrefs.GetString("AvatarType", "preset");
        string avatarValue = PlayerPrefs.GetString("AvatarValue", "0");
        
        if (avatarType == "preset") 
        {
            int index = int.Parse(avatarValue);
            LoadPresetAvatar(index);
        } 
        else if (avatarType == "custom") 
        {
            StartCoroutine(LoadCustomAvatarFromURL(avatarValue));
        }
    }
}

動的コンテンツ表示システム

サーバーから最新のイベント画像やお知らせ画像を取得して表示するシステムです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[System.Serializable]
public class ContentItem 
{
    public string title;
    public string imageURL;
    public string description;
    public System.DateTime publishDate;
}

public class DynamicContentSystem : MonoBehaviour 
{
    [SerializeField] private Transform contentParent;
    [SerializeField] private GameObject contentItemPrefab;
    [SerializeField] private string apiEndpoint = "https://api.example.com/content";
    
    void Start() 
    {
        StartCoroutine(LoadDynamicContent());
    }
    
    IEnumerator LoadDynamicContent() 
    {
        // API からコンテンツ情報を取得
        UnityWebRequest request = UnityWebRequest.Get(apiEndpoint);
        yield return request.SendWebRequest();
        
        if (request.result == UnityWebRequest.Result.Success) 
        {
            string jsonResponse = request.downloadHandler.text;
            ContentItem[] contentItems = JsonHelper.FromJson<ContentItem>(jsonResponse);
            
            // 各コンテンツアイテムを表示
            foreach (ContentItem item in contentItems) 
            {
                yield return StartCoroutine(CreateContentItem(item));
            }
        }
        
        request.Dispose();
    }
    
    IEnumerator CreateContentItem(ContentItem item) 
    {
        // プレファブからUIオブジェクトを生成
        GameObject contentObj = Instantiate(contentItemPrefab, contentParent);
        
        // UI要素を取得
        Text titleText = contentObj.transform.Find("Title").GetComponent<Text>();
        Text descriptionText = contentObj.transform.Find("Description").GetComponent<Text>();
        RawImage contentImage = contentObj.transform.Find("Image").GetComponent<RawImage>();
        
        // テキスト情報を設定
        titleText.text = item.title;
        descriptionText.text = item.description;
        
        // 画像を非同期で読み込み
        yield return StartCoroutine(LoadContentImage(item.imageURL, contentImage));
    }
    
    IEnumerator LoadContentImage(string imageURL, RawImage targetImage) 
    {
        if (string.IsNullOrEmpty(imageURL)) 
            yield break;
            
        UnityWebRequest request = UnityWebRequestTexture.GetTexture(imageURL);
        yield return request.SendWebRequest();
        
        if (request.result == UnityWebRequest.Result.Success) 
        {
            Texture2D texture = DownloadHandlerTexture.GetContent(request);
            targetImage.texture = texture;
        } 
        else 
        {
            // デフォルト画像を設定
            targetImage.texture = Resources.Load<Texture2D>("DefaultImages/no_image");
        }
        
        request.Dispose();
    }
}

// JSONの配列をパースするためのヘルパークラス
public static class JsonHelper 
{
    public static T[] FromJson<T>(string json) 
    {
        Wrapper<T> wrapper = JsonUtility.FromJson<Wrapper<T>>(json);
        return wrapper.Items;
    }
    
    [System.Serializable]
    private class Wrapper<T> 
    {
        public T[] Items;
    }
}

スクリーンショット機能

ゲーム内でスクリーンショットを撮影し、それを画像として保存・表示する機能です。

using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.UI;

public class ScreenshotSystem : MonoBehaviour 
{
    [SerializeField] private Button screenshotButton;
    [SerializeField] private RawImage previewImage;
    [SerializeField] private Camera screenshotCamera;
    
    private string screenshotDirectory;
    
    void Start() 
    {
        screenshotDirectory = Path.Combine(Application.persistentDataPath, "Screenshots");
        
        // ディレクトリが存在しない場合は作成
        if (!Directory.Exists(screenshotDirectory)) 
        {
            Directory.CreateDirectory(screenshotDirectory);
        }
        
        screenshotButton.onClick.AddListener(CaptureScreenshot);
    }
    
    void CaptureScreenshot() 
    {
        StartCoroutine(CaptureScreenshotCoroutine());
    }
    
    IEnumerator CaptureScreenshotCoroutine() 
    {
        // スクリーンショット用のカメラ設定
        if (screenshotCamera == null) 
        {
            screenshotCamera = Camera.main;
        }
        
        // レンダーテクスチャを作成
        RenderTexture renderTexture = new RenderTexture(1920, 1080, 24);
        screenshotCamera.targetTexture = renderTexture;
        
        // カメラでレンダリング
        screenshotCamera.Render();
        
        // テクスチャを読み取り
        RenderTexture.active = renderTexture;
        Texture2D screenshot = new Texture2D(1920, 1080, TextureFormat.RGB24, false);
        screenshot.ReadPixels(new Rect(0, 0, 1920, 1080), 0, 0);
        screenshot.Apply();
        
        // カメラの設定をリセット
        screenshotCamera.targetTexture = null;
        RenderTexture.active = null;
        
        // ファイルに保存
        string fileName = $"screenshot_{System.DateTime.Now:yyyyMMdd_HHmmss}.png";
        string filePath = Path.Combine(screenshotDirectory, fileName);
        
        byte[] pngData = screenshot.EncodeToPNG();
        File.WriteAllBytes(filePath, pngData);
        
        // プレビューに表示
        previewImage.texture = screenshot;
        
        Debug.Log($"スクリーンショットを保存しました: {filePath}");
        
        // リソースの解放
        DestroyImmediate(renderTexture);
        
        yield return null;
    }
    
    // 保存されたスクリーンショット一覧を取得
    public string[] GetSavedScreenshots() 
    {
        if (Directory.Exists(screenshotDirectory)) 
        {
            return Directory.GetFiles(screenshotDirectory, "*.png");
        }
        
        return new string[0];
    }
    
    // 保存されたスクリーンショットを読み込み
    public IEnumerator LoadSavedScreenshot(string filePath, RawImage targetImage) 
    {
        if (!File.Exists(filePath)) 
        {
            Debug.LogError($"ファイルが見つかりません: {filePath}");
            yield break;
        }
        
        byte[] imageData = File.ReadAllBytes(filePath);
        Texture2D texture = new Texture2D(2, 2);
        
        if (texture.LoadImage(imageData)) 
        {
            targetImage.texture = texture;
        } 
        else 
        {
            Debug.LogError("画像の読み込みに失敗しました");
        }
        
        yield return null;
    }
}

高度な画像処理テクニック

画像のリサイズ処理

大きすぎる画像を適切なサイズにリサイズする機能です。

public static class TextureUtils 
{
    public static Texture2D ResizeTexture(Texture2D originalTexture, int targetWidth, int targetHeight) 
    {
        RenderTexture renderTexture = RenderTexture.GetTemporary(targetWidth, targetHeight);
        Graphics.Blit(originalTexture, renderTexture);
        
        RenderTexture previous = RenderTexture.active;
        RenderTexture.active = renderTexture;
        
        Texture2D resizedTexture = new Texture2D(targetWidth, targetHeight);
        resizedTexture.ReadPixels(new Rect(0, 0, targetWidth, targetHeight), 0, 0);
        resizedTexture.Apply();
        
        RenderTexture.active = previous;
        RenderTexture.ReleaseTemporary(renderTexture);
        
        return resizedTexture;
    }
    
    public static Texture2D CropTexture(Texture2D originalTexture, Rect cropArea) 
    {
        Color[] pixels = originalTexture.GetPixels(
            (int)cropArea.x, 
            (int)cropArea.y, 
            (int)cropArea.width, 
            (int)cropArea.height
        );
        
        Texture2D croppedTexture = new Texture2D((int)cropArea.width, (int)cropArea.height);
        croppedTexture.SetPixels(pixels);
        croppedTexture.Apply();
        
        return croppedTexture;
    }
}

キャッシュシステム

一度読み込んだ画像をメモリに保持して、再利用するシステムです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class ImageCache : MonoBehaviour 
{
    private static ImageCache instance;
    public static ImageCache Instance 
    {
        get 
        {
            if (instance == null) 
            {
                GameObject cacheObject = new GameObject("ImageCache");
                instance = cacheObject.AddComponent<ImageCache>();
                DontDestroyOnLoad(cacheObject);
            }
            return instance;
        }
    }
    
    private Dictionary<string, Texture2D> cachedTextures = new Dictionary<string, Texture2D>();
    private Dictionary<string, Coroutine> loadingCoroutines = new Dictionary<string, Coroutine>();
    
    // 最大キャッシュサイズ(メガバイト)
    [SerializeField] private float maxCacheSizeMB = 100f;
    private float currentCacheSizeMB = 0f;
    
    // キャッシュから画像を取得
    public Texture2D GetCachedTexture(string key) 
    {
        if (cachedTextures.ContainsKey(key)) 
        {
            Debug.Log($"キャッシュから画像を取得: {key}");
            return cachedTextures[key];
        }
        
        return null;
    }
    
    // 画像をキャッシュに追加
    public void CacheTexture(string key, Texture2D texture) 
    {
        if (texture == null) return;
        
        // 既にキャッシュされている場合は更新
        if (cachedTextures.ContainsKey(key)) 
        {
            RemoveFromCache(key);
        }
        
        // メモリサイズをチェック
        float textureSizeMB = GetTextureSizeMB(texture);
        
        // キャッシュサイズがオーバーする場合は古いものを削除
        while (currentCacheSizeMB + textureSizeMB > maxCacheSizeMB && cachedTextures.Count > 0) 
        {
            RemoveOldestFromCache();
        }
        
        cachedTextures[key] = texture;
        currentCacheSizeMB += textureSizeMB;
        
        Debug.Log($"画像をキャッシュに追加: {key} (サイズ: {textureSizeMB:F2}MB)");
    }
    
    // 非同期で画像を読み込んでキャッシュ
    public IEnumerator LoadAndCacheImage(string url, System.Action<Texture2D> onComplete = null) 
    {
        // 既にキャッシュされている場合
        Texture2D cachedTexture = GetCachedTexture(url);
        if (cachedTexture != null) 
        {
            onComplete?.Invoke(cachedTexture);
            yield break;
        }
        
        // 既に読み込み中の場合は待機
        if (loadingCoroutines.ContainsKey(url)) 
        {
            yield return loadingCoroutines[url];
            
            cachedTexture = GetCachedTexture(url);
            if (cachedTexture != null) 
            {
                onComplete?.Invoke(cachedTexture);
            }
            yield break;
        }
        
        // 新規読み込み
        Coroutine loadCoroutine = StartCoroutine(LoadImageCoroutine(url, onComplete));
        loadingCoroutines[url] = loadCoroutine;
    }
    
    private IEnumerator LoadImageCoroutine(string url, System.Action<Texture2D> onComplete) 
    {
        UnityWebRequest request = UnityWebRequestTexture.GetTexture(url);
        yield return request.SendWebRequest();
        
        if (request.result == UnityWebRequest.Result.Success) 
        {
            Texture2D texture = DownloadHandlerTexture.GetContent(request);
            CacheTexture(url, texture);
            onComplete?.Invoke(texture);
        } 
        else 
        {
            Debug.LogError($"画像の読み込みに失敗: {url} - {request.error}");
            onComplete?.Invoke(null);
        }
        
        // 読み込み完了後はコルーチンリストから削除
        if (loadingCoroutines.ContainsKey(url)) 
        {
            loadingCoroutines.Remove(url);
        }
        
        request.Dispose();
    }
    
    // キャッシュから削除
    public void RemoveFromCache(string key) 
    {
        if (cachedTextures.ContainsKey(key)) 
        {
            Texture2D texture = cachedTextures[key];
            float sizeMB = GetTextureSizeMB(texture);
            
            DestroyImmediate(texture);
            cachedTextures.Remove(key);
            currentCacheSizeMB -= sizeMB;
            
            Debug.Log($"キャッシュから削除: {key}");
        }
    }
    
    // 最も古いキャッシュを削除
    private void RemoveOldestFromCache() 
    {
        if (cachedTextures.Count > 0) 
        {
            var enumerator = cachedTextures.GetEnumerator();
            enumerator.MoveNext();
            string oldestKey = enumerator.Current.Key;
            RemoveFromCache(oldestKey);
            enumerator.Dispose();
        }
    }
    
    // テクスチャのメモリサイズを計算
    private float GetTextureSizeMB(Texture2D texture) 
    {
        if (texture == null) return 0f;
        
        int pixels = texture.width * texture.height;
        int bytesPerPixel = 4; // RGBA32の場合
        
        switch (texture.format) 
        {
            case TextureFormat.RGB24:
                bytesPerPixel = 3;
                break;
            case TextureFormat.RGBA32:
                bytesPerPixel = 4;
                break;
            case TextureFormat.DXT1:
                bytesPerPixel = 1; // 圧縮形式
                break;
            case TextureFormat.DXT5:
                bytesPerPixel = 1; // 圧縮形式
                break;
        }
        
        float sizeBytes = pixels * bytesPerPixel;
        return sizeBytes / (1024f * 1024f); // MBに変換
    }
    
    // キャッシュの状態を取得
    public void LogCacheStatus() 
    {
        Debug.Log($"キャッシュ状況 - アイテム数: {cachedTextures.Count}, " +
                 $"使用メモリ: {currentCacheSizeMB:F2}MB / {maxCacheSizeMB}MB");
    }
    
    // すべてのキャッシュをクリア
    public void ClearCache() 
    {
        foreach (var kvp in cachedTextures) 
        {
            if (kvp.Value != null) 
            {
                DestroyImmediate(kvp.Value);
            }
        }
        
        cachedTextures.Clear();
        currentCacheSizeMB = 0f;
        Debug.Log("すべてのキャッシュをクリアしました");
    }
    
    void OnDestroy() 
    {
        ClearCache();
    }
}

使用例:キャッシュを活用した画像表示

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class CachedImageDisplay : MonoBehaviour 
{
    [SerializeField] private RawImage displayImage;
    [SerializeField] private string imageURL;
    
    void Start() 
    {
        LoadImage();
    }
    
    void LoadImage() 
    {
        StartCoroutine(ImageCache.Instance.LoadAndCacheImage(imageURL, OnImageLoaded));
    }
    
    void OnImageLoaded(Texture2D texture) 
    {
        if (texture != null) 
        {
            displayImage.texture = texture;
        } 
        else 
        {
            Debug.LogError("画像の読み込みに失敗しました");
        }
    }
}

パフォーマンス最適化のコツ

テクスチャ圧縮の活用

public class TextureOptimizer 
{
    public static void OptimizeTexture(Texture2D texture) 
    {
        // テクスチャの設定を最適化
        texture.Compress(true); // 圧縮を有効化
        texture.filterMode = FilterMode.Bilinear; // 適度なフィルタリング
        texture.anisoLevel = 1; // 異方性フィルタリングを最小に
    }
    
    public static Texture2D CreateOptimizedTexture(byte[] imageData, int maxSize = 1024) 
    {
        Texture2D texture = new Texture2D(2, 2);
        
        if (texture.LoadImage(imageData)) 
        {
            // サイズが大きすぎる場合はリサイズ
            if (texture.width > maxSize || texture.height > maxSize) 
            {
                float scale = Mathf.Min((float)maxSize / texture.width, (float)maxSize / texture.height);
                int newWidth = Mathf.RoundToInt(texture.width * scale);
                int newHeight = Mathf.RoundToInt(texture.height * scale);
                
                Texture2D resized = TextureUtils.ResizeTexture(texture, newWidth, newHeight);
                DestroyImmediate(texture);
                texture = resized;
            }
            
            OptimizeTexture(texture);
            return texture;
        }
        
        DestroyImmediate(texture);
        return null;
    }
}

非同期読み込みの最適化

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BatchImageLoader : MonoBehaviour 
{
    [SerializeField] private int maxConcurrentLoads = 3; // 同時読み込み数制限
    
    private Queue<ImageLoadRequest> loadQueue = new Queue<ImageLoadRequest>();
    private List<Coroutine> activeLoads = new List<Coroutine>();
    
    private struct ImageLoadRequest 
    {
        public string url;
        public System.Action<Texture2D> callback;
    }
    
    public void QueueImageLoad(string url, System.Action<Texture2D> callback) 
    {
        loadQueue.Enqueue(new ImageLoadRequest { url = url, callback = callback });
        ProcessQueue();
    }
    
    void ProcessQueue() 
    {
        // 同時読み込み数が上限に達している場合は待機
        if (activeLoads.Count >= maxConcurrentLoads) 
            return;
            
        // キューから次のリクエストを取得
        if (loadQueue.Count > 0) 
        {
            ImageLoadRequest request = loadQueue.Dequeue();
            Coroutine loadCoroutine = StartCoroutine(LoadImageWithCleanup(request));
            activeLoads.Add(loadCoroutine);
        }
    }
    
    IEnumerator LoadImageWithCleanup(ImageLoadRequest request) 
    {
        yield return StartCoroutine(ImageCache.Instance.LoadAndCacheImage(request.url, request.callback));
        
        // 完了後にアクティブリストから削除
        activeLoads.RemoveAll(c => c == null);
        
        // 次のキューを処理
        ProcessQueue();
    }
}

デバッグとログ機能

詳細なログシステム

using UnityEngine;

public static class ImageLoadLogger 
{
    public enum LogLevel 
    {
        Info,
        Warning,
        Error
    }
    
    public static bool enableLogging = true;
    
    public static void Log(string message, LogLevel level = LogLevel.Info) 
    {
        if (!enableLogging) return;
        
        string timestamp = System.DateTime.Now.ToString("HH:mm:ss.fff");
        string formattedMessage = $"[ImageLoader {timestamp}] {message}";
        
        switch (level) 
        {
            case LogLevel.Info:
                Debug.Log(formattedMessage);
                break;
            case LogLevel.Warning:
                Debug.LogWarning(formattedMessage);
                break;
            case LogLevel.Error:
                Debug.LogError(formattedMessage);
                break;
        }
    }
    
    public static void LogLoadStart(string source) 
    {
        Log($"画像読み込み開始: {source}");
    }
    
    public static void LogLoadSuccess(string source, float loadTime) 
    {
        Log($"画像読み込み成功: {source} (時間: {loadTime:F2}秒)");
    }
    
    public static void LogLoadError(string source, string error) 
    {
        Log($"画像読み込み失敗: {source} - エラー: {error}", LogLevel.Error);
    }
}

パフォーマンス測定

using System.Collections;
using UnityEngine;

public class ImageLoadProfiler : MonoBehaviour 
{
    private float totalLoadTime = 0f;
    private int totalLoads = 0;
    private int successfulLoads = 0;
    private int failedLoads = 0;
    
    public IEnumerator LoadImageWithProfiling(string url, System.Action<Texture2D> callback) 
    {
        float startTime = Time.realtimeSinceStartup;
        totalLoads++;
        
        ImageLoadLogger.LogLoadStart(url);
        
        yield return StartCoroutine(ImageCache.Instance.LoadAndCacheImage(url, (texture) => {
            float loadTime = Time.realtimeSinceStartup - startTime;
            totalLoadTime += loadTime;
            
            if (texture != null) 
            {
                successfulLoads++;
                ImageLoadLogger.LogLoadSuccess(url, loadTime);
            } 
            else 
            {
                failedLoads++;
                ImageLoadLogger.LogLoadError(url, "テクスチャがnull");
            }
            
            callback?.Invoke(texture);
        }));
    }
    
    public void LogStatistics() 
    {
        float averageLoadTime = totalLoads > 0 ? totalLoadTime / totalLoads : 0f;
        float successRate = totalLoads > 0 ? (float)successfulLoads / totalLoads * 100f : 0f;
        
        Debug.Log($"画像読み込み統計:");
        Debug.Log($"  総読み込み数: {totalLoads}");
        Debug.Log($"  成功: {successfulLoads}, 失敗: {failedLoads}");
        Debug.Log($"  成功率: {successRate:F1}%");
        Debug.Log($"  平均読み込み時間: {averageLoadTime:F2}秒");
        Debug.Log($"  総読み込み時間: {totalLoadTime:F2}秒");
    }
    
    void OnApplicationPause(bool pauseStatus) 
    {
        if (pauseStatus) 
        {
            LogStatistics();
        }
    }
}

よくある問題とその解決方法

問題1:メモリリークの発生

症状

  • アプリの動作が徐々に重くなる
  • メモリ使用量が増加し続ける
  • 最終的にクラッシュする

原因

  • 読み込んだテクスチャを適切に解放していない
  • キャッシュシステムが無制限に増大している

解決方法

public class MemoryManagedImageLoader : MonoBehaviour 
{
    private List<Texture2D> managedTextures = new List<Texture2D>();
    
    public void LoadImageSafely(string path, RawImage target) 
    {
        // 古いテクスチャを解放
        if (target.texture != null && managedTextures.Contains(target.texture as Texture2D)) 
        {
            DestroyImmediate(target.texture);
            managedTextures.Remove(target.texture as Texture2D);
        }
        
        // 新しいテクスチャを読み込み
        Texture2D newTexture = Resources.Load<Texture2D>(path);
        if (newTexture != null) 
        {
            target.texture = newTexture;
            managedTextures.Add(newTexture);
        }
    }
    
    void OnDestroy() 
    {
        // 管理中のテクスチャをすべて解放
        foreach (Texture2D texture in managedTextures) 
        {
            if (texture != null) 
            {
                DestroyImmediate(texture);
            }
        }
        managedTextures.Clear();
    }
}

問題2:ネットワークエラーの処理

症状

  • 外部画像が読み込めない
  • タイムアウトが発生する
  • エラーメッセージが不明確

解決方法

public class RobustNetworkImageLoader : MonoBehaviour 
{
    [SerializeField] private int maxRetryAttempts = 3;
    [SerializeField] private float retryDelaySeconds = 2f;
    
    public IEnumerator LoadImageWithRetry(string url, System.Action<Texture2D> onSuccess, System.Action<string> onFailure) 
    {
        int attempts = 0;
        
        while (attempts < maxRetryAttempts) 
        {
            attempts++;
            
            UnityWebRequest request = UnityWebRequestTexture.GetTexture(url);
            request.timeout = 10; // 10秒でタイムアウト
            
            yield return request.SendWebRequest();
            
            if (request.result == UnityWebRequest.Result.Success) 
            {
                Texture2D texture = DownloadHandlerTexture.GetContent(request);
                onSuccess?.Invoke(texture);
                request.Dispose();
                yield break;
            }
            else 
            {
                string errorMessage = $"試行 {attempts}/{maxRetryAttempts}: {request.error}";
                ImageLoadLogger.Log(errorMessage, ImageLoadLogger.LogLevel.Warning);
                
                request.Dispose();
                
                // 最後の試行でない場合は待機してからリトライ
                if (attempts < maxRetryAttempts) 
                {
                    yield return new WaitForSeconds(retryDelaySeconds);
                }
            }
        }
        
        // すべての試行が失敗した場合
        onFailure?.Invoke($"画像の読み込みに失敗しました({maxRetryAttempts}回試行)");
    }
}

問題3:異なるプラットフォームでの動作差異

症状

  • PCでは動作するがモバイルで動作しない
  • プラットフォーム固有のエラーが発生

解決方法

public class CrossPlatformImageLoader : MonoBehaviour 
{
    public IEnumerator LoadImageCrossPlatform(string path, System.Action<Texture2D> callback) 
    {
        #if UNITY_ANDROID && !UNITY_EDITOR
            // Android固有の処理
            yield return StartCoroutine(LoadImageAndroid(path, callback));
        #elif UNITY_IOS && !UNITY_EDITOR
            // iOS固有の処理
            yield return StartCoroutine(LoadImageIOS(path, callback));
        #elif UNITY_WEBGL && !UNITY_EDITOR
            // WebGL固有の処理
            yield return StartCoroutine(LoadImageWebGL(path, callback));
        #else
            // PC(エディタ含む)の処理
            yield return StartCoroutine(LoadImagePC(path, callback));
        #endif
    }
    
    IEnumerator LoadImageAndroid(string path, System.Action<Texture2D> callback) 
    {
        // AndroidのStreamingAssetsは jar:file:// プロトコルを使用
        string fullPath = path.Contains("://") ? path : "jar:file://" + Application.dataPath + "!/assets/" + path;
        
        UnityWebRequest request = UnityWebRequest.Get(fullPath);
        yield return request.SendWebRequest();
        
        if (request.result == UnityWebRequest.Result.Success) 
        {
            byte[] data = request.downloadHandler.data;
            Texture2D texture = new Texture2D(2, 2);
            texture.LoadImage(data);
            callback?.Invoke(texture);
        }
        
        request.Dispose();
    }
    
    IEnumerator LoadImageIOS(string path, System.Action<Texture2D> callback) 
    {
        // iOS固有の処理(基本的にはPCと同じ)
        yield return StartCoroutine(LoadImagePC(path, callback));
    }
    
    IEnumerator LoadImageWebGL(string path, System.Action<Texture2D> callback) 
    {
        // WebGLではローカルファイルアクセスに制限があるため、
        // StreamingAssetsのファイルもWebリクエストとして扱う
        string fullPath = System.IO.Path.Combine(Application.streamingAssetsPath, path);
        
        UnityWebRequest request = UnityWebRequestTexture.GetTexture(fullPath);
        yield return request.SendWebRequest();
        
        if (request.result == UnityWebRequest.Result.Success) 
        {
            Texture2D texture = DownloadHandlerTexture.GetContent(request);
            callback?.Invoke(texture);
        }
        
        request.Dispose();
    }
    
    IEnumerator LoadImagePC(string path, System.Action<Texture2D> callback) 
    {
        string fullPath = System.IO.Path.Combine(Application.streamingAssetsPath, path);
        
        if (System.IO.File.Exists(fullPath)) 
        {
            byte[] data = System.IO.File.ReadAllBytes(fullPath);
            Texture2D texture = new Texture2D(2, 2);
            
            if (texture.LoadImage(data)) 
            {
                callback?.Invoke(texture);
            }
        }
        
        yield return null;
    }
}

まとめ:Unity画像読み込みのベストプラクティス

Unityで画像を読み込む方法について、基本から応用まで詳しく解説してきました。重要なポイントをまとめます:

基本的な読み込み方法の使い分け

Resourcesフォルダ

  • 小規模プロジェクトや固定画像に適している
  • 実装が簡単で確実
  • ビルドサイズが大きくなるデメリット

StreamingAssets

  • ビルド後でも画像の差し替えが可能
  • プラットフォーム固有の処理が必要
  • 中〜大規模プロジェクトに適している

外部URL

  • 動的コンテンツや最新画像の取得に最適
  • ネットワークエラーやセキュリティの考慮が必要
  • ソーシャル機能や配信コンテンツに活用

パフォーマンス最適化の要点

メモリ管理

  • 使用後のテクスチャは必ず解放
  • キャッシュシステムでメモリ使用量を制御
  • テクスチャ圧縮の活用

非同期処理

  • UI の応答性を保つため必須
  • 複数画像の並行読み込み制御
  • エラーハンドリングの充実

プラットフォーム対応

  • 各プラットフォームの特性を理解
  • 条件付きコンパイルの活用
  • クロスプラットフォームテストの実施

実用的な活用シーン

ユーザーカスタマイズ

  • アバターシステム
  • カスタム背景
  • ユーザー生成コンテンツ

動的コンテンツ

  • イベント画像の配信
  • アップデート情報の表示
  • ソーシャル機能との連携

開発支援ツール

  • スクリーンショット機能
  • デバッグ用画像表示
  • パフォーマンス監視

コメント

タイトルとURLをコピーしました