Help us understand the problem. What is going on with this article?

【Unity】Unity2019におけるImageのSprite変更で生じる深刻なバグとその回避方法

この記事を読む前に

Unity2019.1.5でこの記事で紹介するバグが修正されました。
以下で紹介するバグはUnity2019.1.0~Unity2019.1.4で発生するバグです。

はじめに

UnityでUI.Imageを使う時はスクリプトからSprite、つまり画像を変更させる場面が多々ある。その時は以下のようなコードで画像を変更させていただろう。

public Image Image1;
public Sprite Sprite1;

private void Hoge ()
{
    Image1.sprite = Sprite1;
}

上記のコードは何の捻りも無いコードであり、ImageのSpriteを問題無く変更可能である。
Unity2018までは。
残念な事にUnity2019では上記のコードでは画像の表示でバグが生じる。

本記事ではspriteにSpriteを代入した際に生じるバグとその回避方法について紹介する。
なお、紛らわしさ回避のためにImageの変数であるspriteは背景をこのように灰色にして表示し、グラフィックスオブジェクトを示すSpriteは先頭を大文字にして背景無し、または単に画像と示す。

spriteに画像を代入すると発生するバグについて

バグの検証環境

Unity2019でspriteを使用して画像を変えるとどんなバグが発生するのか説明する。筆者のUnityバージョンはUnity2019.1.4である。
まず、ゲーム画面を以下のように設定する。
image2.png
ご覧の通り、圧倒的巨乳の女の子のImageが4つ、Buttonが1つある。Imageは左から順に1,2,3,4である。Imageはそれぞれ別のSpriteが割り当てられており、RectTransformはX座標以外全て同一である。また、以下に示すスクリプトを用意し、ButtonPush関数をButtonを押した時に呼ぶように設定する。

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

public class ImageTest : MonoBehaviour
{
    public Image [] Images;
    public Sprite [] Sprites;

    private int ButtonPushCount;//ボタンが押された回数

    public void ButtonPush ()
    {
        ButtonPushCount++;
        for (int i = 0; i < Images.Length; i++)
        {
            //Sprite変更
            Images [i].sprite = Sprites [(ButtonPushCount + i) % Sprites.Length];
        }
    }
}

Imagesには4つのImageを左から順に代入し、Spritesは4つのImageのspriteに最初からアタッチされているSpriteを左から順に代入する。各Spriteのサイズなどを以下の表に示す。

Sprite 上の画像での位置 サイズ(横px*縦px) 画像名
Sprites[0] 一番左 368*600 Yuyuko
Sprites[1] 左から二番目 388*702 Suwako_1
Sprites[2] 右から二番目 614*598 Mima
Sprites[3] 一番右 292*700 Keine_1

ButtonPushが呼ばれた時、つまりボタンを押した時はImageのspriteが左に送られる動作をする。

画像のアスペクト比がバグる

Playを開始してButtonを押したら以下の通りになる。
gif1.gif
ボタン押下1回目及び4n+1回目(nは自然数)は綺麗に表示されているが、それ以外はアスペクト比が明らかにおかしい。常にImages[0]とImages[2]が縦に大きく、Images[1]が横に大きい。
よく観察すると分かるが、ContentSizeFitterコンポーネントがあるにも関わらず画像サイズがボタン押下1回目の画像サイズで固定されてしまっているのだ。

今回のスクリプトに限らず、(筆者が確認した限りでは)どのような状況においてもこのバグが発生した。

色もバグってる

spriteでは画像のサイズだけでなく色も固定されてしまう。それも確認するため、上記のスクリプトのButtonPush関数に以下に示すように色を変化させる機能も追加する。

public void ButtonPush ()
{
    ButtonPushCount++;
    for (int i = 0; i < Images.Length; i++)
    {
        //Sprite変更
        Images [i].sprite = Sprites [(ButtonPushCount + i) % Sprites.Length];

        //色変更
        switch (ButtonPushCount % Sprites.Length)
        {
        case 0:
        default:
            Images [i].color = new Color (0.5f, 0.5f, 0.5f, 0.5f);
            break;
        case 1:
            Images [i].color = Color.red;
            break;
        case 2:
            Images [i].color = Color.green;
            break;
        case 3:
            Images [i].color = Color.blue;
            break;
        }
    }
}

Play画面はこちら。今回はインスペクターも見えるようにした。
gif2.gif
インスペクターを見る限りではサイズと色がボタンを押すたびに変更されている。しかしゲーム画面ではサイズはさっき示した通り、色は見れば分かる通りButton押下1回目の赤色で固定されてしまっている。

以上の通り、画像のサイズと色で盛大にバグる。
画像の表示というのはどのゲーム(特にソシャゲやギャルゲーなどのキャラゲー)において最も重要な要素の1つだと考えられるので、このバグは極めて深刻なバグであると言えよう。

バグの回避策

このバグの回避策を探した所、以下の処理のどれかを行う事でバグの回避が可能であると分かった。

  1. spriteに画像を代入する直前にspriteにnullを代入する
  2. spriteにではなくoverrideSpriteに画像を代入する
  3. ImageのtypeをFilledにする

nullを代入する方法

nullを代入してから画像を代入する事でバグを回避可能である。

Images [i].sprite = null;
Images [i].sprite = Sprites [(ButtonPushCount + i) % Sprites.Length];

他の方法と異なりバグの温床にもならないので、原則としてこれを使うべきだろう。

overrideSpriteを使う方法

overrideSpriteにSpriteを代入した際はこのバグは発生しない。この変数は読んで字の如く、spriteをオーバーライドして使用するSpriteを示す変数である。聞き慣れない人も多いだろうし実際にこれに触れた記事は非常に少なかったが、Unity5の時代からある結構古い変数である。

Images [i].overrideSprite = Sprites [(ButtonPushCount + i) % Sprites.Length];

spriteの記述方法まんまである。
注意として、overrideSpriteはインスペクター上から変更できず、overrideSpriteにnullを代入した場合はspriteの画像が表示される。

typeをFilledにする方法

typeをFilledにすることでもバグが回避可能である。インスペクター上では以下の画像のように設定する。
image3.png
fillMethodなどの値は何でも良い。
スクリプトからは以下のようにして設定可能である。

Images [i].type = Image.Type.Filled;

バグの回避処理を行った結果

上記どれかのバグ回避処理を行い、Playを開始してButtonを押したら以下の通りになる。
gif3.gif
おお、バグってない!
色も変更させ、インスペクターも表示した場合は以下のようになる。なお、以下ではoverrideSpriteを使ったバグ回避をしている。
gif4.gif

では既存のコードを書き換えてバグ回避処理を行うべきか?

バグ回避処理が分かったのは良いが、全てのspriteへの画像代入の場面でバグ回避処理を行うべきではない。理由として

  • バグ回避処理追加の作業自体が非常に面倒
  • Unity公式がこのバグを修正した時にバグ回避処理を削除すると、誤って別のコードも削除して別のバグを発生させかねない
  • だからと言って残したままにするとバグ回避処理が無駄な処理として実行されるだけである

なのでコード中のspriteへの画像代入処理を全て書き換えるのではない、別の方法によりバグ回避をした方が良いだろう。
その方法として、ゲーム中の全てのImageオブジェクトを取得し、バグ回避処理を行うコードを書く事が考えられる。具体的には以下に示すようなコードである。

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

public class ImageBugFixer : MonoBehaviour
{
#if UNITY_2019_1
    private static ImageBugFixer instance = null;//シングルトン
    private Sprite sprite;//一時キャッシュ用

    private void Awake ()
    {
        if (instance == null)
        {
            instance = this;
        }
        else if (instance != this)
        {
            Destroy (gameObject);
        }
        DontDestroyOnLoad (gameObject);
    }

    //Update、FixedUpdate、OnRenderObjectだと修正前の画像が一瞬見えてしまう(特にコルーチン)
    private void LateUpdate ()
    {
        foreach (Image img in FindObjectsOfType<Image>())
        {
            //シーン上に存在し、spriteが割り当てられているか
            if (img.gameObject.activeInHierarchy && img.sprite != null)
            {
                sprite = img.sprite;
                img.sprite = null;
                img.sprite = sprite;
            }
        }
    }
#else
    private void Awake ()
    {
        Debug.Log ("ImageBugFixerはUnity2019.1のみ動作します");
        Debug.Log ("現在のバージョンでも動作させたい場合はImageBugFixer.csの8行目を「#if UNITY_2019_2」などに変更してください");
        Destroy (gameObject);
    }
#endif
}

使い方として、ゲーム中に最初に読み込まれるシーンに空のゲームオブジェクトを作り、それに上記のスクリプトをアタッチするだけである。
image4.png
こうする事でゲーム中の全てのImageについてバグ回避処理が行われる。Unity公式がバグを修正した時はこのゲームオブジェクトを削除してあげることで万事解決する。ただ、注意点としてLateUpdate関数内でFind関数を呼び出す、極めて重い処理を行っている。毎フレーム重い処理を行っているので、ゲームによってはパフォーマンスが大きく悪化してしまう。

まとめ

Unity2019でImageのspriteに画像を代入すると、画像のサイズと色がバグる。
そのバグを回避するために上のImageBugFixerスクリプトをゲームで最初に読み込まれるシーンに空のオブジェクトを作り、それにアタッチすれば良い。

……ただ、このバグ修正のスクリプトは非常に重いのでUnityをまだ2019にアップデートしていない人はアップデートを見送り、もう2019にアップデートしてしまった人はUnity公式がバグを修正してくれるように報告したり祈ったりすべきだろう。
<追記>
記事の最初に書いた通り、Unity2019.1.5でバグが修正された。
UnityバージョンがUnity2019の人はUnity2019.1.5にアップデートすべきだろう。バグ修正のアプデの度に新しいバグが出るのはUnityではよくある事なので心配な人は時間を置いてからアプデしよう

TD12734
Unity/C#を用いて東方二次創作ゲームの制作を行っています。
https://twitter.com/TD12734
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした