OnPopulateMeshで動的に生成したメッシュが更新されなくて焦った話

# はじめに

　内容はタイトルの通りです。`Graphic`を継承したクラスを作り、uGUIで動的にメッシュを生成しておえかきをしたのはよかったのですが、Inspector上で値を変更するとメッシュに反映されるけど、スクリプト内で変更するとメッシュが更新されなくて焦りました。
　なお、Unityのバージョンは2019.3.0f3です。

# 動的にメッシュを生成する

　その前に、メッシュを動的に生成したい人へそのやり方を書いていきます。わかる方はここを飛ばしてください。参考資料は以下のものがいいと思います。（ちょっと古いけど）

* [uGUIで自由なメッシュを表示する(5.3統合版)](https://cfm-art.sakura.ne.jp/sys/archives/319)
* [【Unity】uGUIでMeshを作り、レーダーチャートを描く](https://blog.naichilab.com/entry/radarchart)

　まず、`Graphic`を継承したクラスを作り、`Canvas`の子孫にある`GameObject`にアタッチします。最低限こんな感じ。

```csharp:sample.cs

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

//RequireComponentで要求しているものは、最低限描写をするのに必要なものなのでつけてる。
[RequireComponent(typeof(CanvasRenderer))]
[RequireComponent(typeof(RectTransform))]
public class Sample : Graphic
{

}

```

　そして、メッシュを作るには`OnPopulateMesh`をオーバーライドします。

```csharp:sample.cs

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

[RequireComponent(typeof(CanvasRenderer))]
[RequireComponent(typeof(RectTransform))]
public class Sample : Graphic
{
    protected override void OnPopulateMesh(VertexHelper vh)
    {

    }
}

```

　オーバーライドした`OnPopulateMesh`の中にメッシュを生成するコードを書くのですが、方法が二種類あります。

## 三角形を元に生成

　こちらの利点は、最適化をしやすいところで、欠点は三角形に分割するのがやや面倒な点です。

```csharp:sample.cs
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        /*メッシュや頂点情報を消去。キャッシュできるならいらない。
          パフォーマンス的にもキャッシュできるならしたほうが良い。
          ただし、毎回生成するのであれば頂点数の上限(65000)に達するので必要。*/
        vh.Clear();
        //頂点情報のstruct
        UIVertex v = UIVertex.simpleVert;
        v.color = Color.Red;

        //いい感じの位置に頂点置く
        v.position = CreatePos(0, 0);
        //頂点情報登録
        vh.AddVert(v);

        v.position = CreatePos(0, 1);
        vh.AddVert(v);
        v.position = CreatePos(1, 0);
        vh.AddVert(v);

        //メッシュの三角形生成。引数は登録した頂点のインデックス。
        vh.AddTriangle(0, 1, 2);
    }

    //いい感じの位置変換関数。左下(0,0)、右上(1,1)として入力するとちょうどよく変換される。
    private Vector2 CreatePos(float x, float y)
    {
        Vector2 p = Vector2.zero;
        p.x -= rectTransform.pivot.x;
        p.y -= rectTransform.pivot.y;
        p.x += x;
        p.y += y;
        p.x *= rectTransform.rect.width;
        p.y *= rectTransform.rect.height;
        return p;
    }
```

## 四角形を元に生成

　こちらの利点は、やや楽な場合が多く、欠点は最適化できないというところです。

```csharp:sample.cs
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        /*メッシュや頂点情報を消去。キャッシュできるならいらない。
          パフォーマンス的にもキャッシュできるならしたほうが良い。
          ただし、毎回生成するのであれば頂点数の上限(65000)に達するので必要。*/
        vh.Clear();
        //頂点情報のstruct
        UIVertex v1 = UIVertex.simpleVert;
        v1.color = Color.Red;
        UIVertex v2 = UIVertex.simpleVert;
        v2.color = Color.Red;
        UIVertex v3 = UIVertex.simpleVert;
        v3.color = Color.Red;
        UIVertex v4 = UIVertex.simpleVert;
        v4.color = Color.Red;

        //いい感じの位置に頂点置く。
        v1.position = CreatePos(0, 0);
        v2.position = CreatePos(0, 1);
        v3.position = CreatePos(1, 1);
        v4.position = CreatePos(1, 0);
        

        //メッシュの四角形生成。四角形を一周する順番の配列でないとおかしなことになるので注意
        vh.AddUIVertexQuad(new UIVertex[]{v1, v2, v3, v4});
    }

    //いい感じの位置変換関数。左下(0,0)、右上(1,1)として入力するとちょうどよく変換される。
    private Vector2 CreatePos(float x, float y)
    {
        Vector2 p = Vector2.zero;
        p.x -= rectTransform.pivot.x;
        p.y -= rectTransform.pivot.y;
        p.x += x;
        p.y += y;
        p.x *= rectTransform.rect.width;
        p.y *= rectTransform.rect.height;
        return p;
    }
```

# 原因

　英語でググったらすぐ出てきました。原因は、`OnPopulateMesh`というのは更新が必要な時にしか実行されず、Inspector上では変数が変更されれば更新が必要なフラグが立つが、シ－ン上では開始時のみ自動でフラグが立ち、以降は変数が変更されてもフラグが立たないということでした。

# 解決法

　ようは、更新したいときにフラグを立てればよいです。

```csharp
    SetVerticesDirty();
```
　上の関数を実行するとフラグが立つようです。
　ただし、一つ気をつけなければいけないことがあり、メッシュ更新中に再度この関数を実行するとエラーが起こるので、適切なタイミングで実行する必要があります。