質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.75%

  • C#

    6552questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

  • Unity

    3576questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

  • Unity3D

    1175questions

    Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

  • Unity2D

    758questions

uGUIのImageを9sliceを維持したまま平行四辺形にしたい

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 239

proelisoft

score 1

 実現したいこと

音ゲーのスライドノーツのようなデザインを実現したい。
一枚の正方形の画像から、9sliceして引き延ばせるようにし、台形に頂点を変形して表示したいです。
>有名音ゲーのスライドについての記事
イメージ説明

バンドリ!ガルパのプレイ画面

 試したこと

ブログ等を参考に、Graphicsを継承したクラスを作成してOnPopulateMeshをオーバーライドして台形にすることはできましたが、外側の枠の部分は引き延ばさない方法がわかりませんでした。
>[Unity5.3]UGUIの画像を変形させる
イメージ説明

Imageを継承したクラスを作成して同様のことをしようとし、公式のリポジトリ等を見ましたが、必要そうな関数がprivateであったりとしてそのようなことは想定されていないのかと判断しました。
>Unity Technologies/Unity/UI/Image

うまく表示する方法を探しています。

 補足情報

Unity2017 3.0f3

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

Imageを継承したカスタムコンポーネントを作り、OnPopulateMesh内でImageが作ったメッシュ頂点をさらに変形してやればいけるのでは、と思いました...が、おっしゃる通りすでにVertexHelperに追加されている頂点を変形させようにも、既存の頂点を取得する方法が用意されていないようです(GetUIVertexStreamは求めるものとは何だか違うようです)。

ちょっと不本意ではありますが、VertexHelperの頂点の位置を格納しているm_Positionsに直接アクセスして書き換えてみました。

本来のImageコンポーネントを削除し、代わりにこちらをアタッチして...

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

public class SkewedImage : Image
{
    public Vector2 Skew; // 水平・垂直スキューのタンジェント...たとえば(0, 1)とすると、X成分の増加に傾き1で比例してYも増加する→X軸が45°傾く

    protected override void OnPopulateMesh(VertexHelper toFill)
    {
        // 本来のImageの頂点を作成させる
        base.OnPopulateMesh(toFill);

        // Imageが作成した頂点を(むりやり)取得
        var positionsInfo = typeof(VertexHelper).GetField(
            "m_Positions",
            BindingFlags.Instance | BindingFlags.NonPublic);
        var positions = positionsInfo.GetValue(toFill) as List<Vector3>;
        var positionCount = positions == null ? 0 : positions.Count;

        // スキュー変換行列を作成
        var skewMatrix = Matrix4x4.identity;
        skewMatrix.m01 = this.Skew.x;
        skewMatrix.m10 = this.Skew.y;

        // 全頂点にスキュー変換を適用
        for (var i = 0; i < positionCount; i++)
        {
            positions[i] = skewMatrix.MultiplyPoint(positions[i]);
        }
    }
}

Imageコンポーネントは独自のエディターを使っているので、こちらもカスタムエディターを用意しEditorフォルダに入れておき...

using UnityEditor;
using UnityEditor.UI;

[CustomEditor(typeof(SkewedImage), true)]
[CanEditMultipleObjects]
public class SkewedImageEditor : ImageEditor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI(); // まず本来のImage用エディターを表示する
        var targetSkewedImage = this.target as SkewedImage;
        if (targetSkewedImage != null)
        {
            var prevSkew = targetSkewedImage.Skew; // 編集前のスキュー
            var newSkew = EditorGUILayout.Vector2Field("Skew", prevSkew); // 編集後のスキュー
            if (newSkew != prevSkew) // 値の編集が行われたならば...
            {
                targetSkewedImage.Skew = newSkew; // スキューを新しい値に更新し...
                targetSkewedImage.SetVerticesDirty(); // メッシュの再生成が必要なことを知らせる
            }
        }
    }
}

これで操作してみると、下図のような感じになりました。
プレビュー

プライベートフィールドへの直接アクセスをせずにやるとなると、自前で9スライス機能を持つコンポーネントを作ることになるでしょうかね。
Imageのコードを改造する手もあるかもしれませんが、Unity Technologiesの公開しているコードは自由に改造していいわけではないらしいので、そちらは控えておいた方がよさそうです。

※ご質問者さんのおっしゃる「台形」というのは、ご提示の図を見て平行四辺形のことだろうと勝手に解釈してしまいましたが、任意の台形(平行でない対辺があるかもしれない)でないとまずかったでしょうか?

 追記

どうやらGetUIVertexStreamを使っても問題なさそうでした。計算量・メモリ消費量は少し増えそうですが(わずかな増加なので大したことはないでしょう)、こっちの方がまともなやり方のように思います。

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

public class SkewedImage : Image
{
    public Vector2 Skew; // 水平・垂直スキューのタンジェント...たとえば(0, 1)とすると、X成分の増加に傾き1で比例してYも増加する→X軸が45°傾く

    private List<UIVertex> vertices;

    protected override void OnPopulateMesh(VertexHelper toFill)
    {
        // 本来のImageの頂点を作成させる
        base.OnPopulateMesh(toFill);

        // Imageが作成した頂点をまともな方法で取得
        // どうやらメッシュがインデックス付きからインデックスなしに変わってしまう?
        // UI用オブジェクトなら、インデックスなしメッシュでもさほど頂点数は増えないだろうと期待する
        if (this.vertices == null)
        {
            this.vertices = new List<UIVertex>();
        }
        toFill.GetUIVertexStream(this.vertices);
        var vertexCount = this.vertices.Count;

        // スキュー変換行列を作成
        var skewMatrix = Matrix4x4.identity;
        skewMatrix.m01 = this.Skew.x;
        skewMatrix.m10 = this.Skew.y;

        // スキュー変換適用後の頂点でメッシュを置き換える
        // せっかくImageが作ったメッシュを破棄してしまっている気がするが、こちらの方が正当なやり方かもしれない
        for (var i = 0; i < vertexCount; i++)
        {
            var v = this.vertices[i];
            v.position = skewMatrix.MultiplyPoint3x4(v.position); // 平行四辺形変形ならMultiplyPoint3x4で対応可能なので、こっちを使った方が少し高速?
            this.vertices[i] = v;
        }
        toFill.Clear();
        toFill.AddUIVertexTriangleStream(this.vertices);
    }
}

 枠線が細く見えることについて追記

確かに傾き量を大きくすると、枠線が細く見えてしまうのは気になりますね。
なんとかして9スライスの区切り位置をずらしてやる必要があるかと思います。あまりエレガントではないですが、ずらし機能を追加してみました。

元のスプライトは9つの四角形が並んだ36個の頂点からなり、これがGetUIVertexStreamを通った後は一つの四角形が2つの三角形になって頂点数は54個になるようです。このうち、コード中のSliceOffsetV0Indicesに示した18頂点が下側のスライス区切り位置に、SliceOffsetV1Indicesに示した18頂点が上側のスライス区切り位置に相当するようなので、これらを見た目がよくなるようY座標を調節できるようにしてみました。

私のコードはスキュー行列方式のままですが、ご質問者さんの方式でも使える方法かと思います。

SkewedImage改変版

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

public class SkewedImage : Image
{
    // スライス区切り位置に相当する頂点のインデックスの表
    private static readonly int[] SliceOffsetV0Indices =
    {
         1,  2,  3,  6, 10, 11,
        19, 20, 21, 24, 28, 29,
        37, 38, 39, 42, 46, 47
    };
    private static readonly int[] SliceOffsetV1Indices =
    {
         7,  8,  9, 12, 16, 17,
        25, 26, 27, 30, 34, 35,
        43, 44, 45, 48, 52, 53
    };

    public Vector2 Skew;

    // スライス区切り座標のずらし量
    public Vector2 SliceOffsetV;

    private List<UIVertex> vertices;

    protected override void OnPopulateMesh(VertexHelper toFill)
    {
        base.OnPopulateMesh(toFill);
        if (this.vertices == null)
        {
            this.vertices = new List<UIVertex>();
        }
        toFill.GetUIVertexStream(this.vertices);
        var vertexCount = this.vertices.Count;
        var skewMatrix = Matrix4x4.identity;
        skewMatrix.m01 = this.Skew.x;
        skewMatrix.m10 = this.Skew.y;

        // スライス区切りインデックス表を参照するためのインデックス
        var sV0 = 0;
        var sV1 = 0;

        for (var i = 0; i < vertexCount; i++)
        {
            var v = this.vertices[i];
            v.position = skewMatrix.MultiplyPoint3x4(v.position);

            // iがスライス区切りインデックス表に載っていれば、Y座標をずらす
            if (i == SliceOffsetV0Indices[sV0])
            {
                // 第1のスライス区切り位置をずらす
                v.position.y += this.SliceOffsetV.x;
                sV0 = Mathf.Min(sV0 + 1, SliceOffsetV0Indices.Length - 1);
            }
            else if (i == SliceOffsetV1Indices[sV1])
            {
                // 第2のスライス区切り位置をずらす
                v.position.y += this.SliceOffsetV.y;
                sV1 = Mathf.Min(sV1 + 1, SliceOffsetV1Indices.Length - 1);
            }

            this.vertices[i] = v;
        }

        toFill.Clear();
        toFill.AddUIVertexTriangleStream(this.vertices);
    }
}

SkewedImageEditor改変版

using UnityEditor;
using UnityEditor.UI;
using UnityEngine;

[CustomEditor(typeof(SkewedImage), true)]
[CanEditMultipleObjects]
public class SkewedImageEditor : ImageEditor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        var targetSkewedImage = this.target as SkewedImage;
        if (targetSkewedImage != null)
        {
            var prevSkew = targetSkewedImage.Skew;
            var newSkew = EditorGUILayout.Vector2Field("Skew", prevSkew);
            var dirty = false;
            if (newSkew != prevSkew)
            {
                targetSkewedImage.Skew = newSkew;
                dirty = true;
            }
            var prevSliceOffsetV = targetSkewedImage.SliceOffsetV;
            var newSliceOffsetV = EditorGUILayout.Vector2Field("Vertical Slice Offset", prevSliceOffsetV);
            if (newSliceOffsetV != prevSliceOffsetV)
            {
                targetSkewedImage.SliceOffsetV = newSliceOffsetV;
                dirty = true;
            }
            if (dirty)
            {
                targetSkewedImage.SetVerticesDirty();
            }
        }
    }
}

動かした様子
プレビュー

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/06/03 13:13

    コード、画像付きの大変丁寧な回答でとてもわかりやすく、感謝しております。
    今回は台形ではなく平行四辺形で実装する予定です。
    base.OnPopulateMesh(toFill); で一旦頂点を生成した後、座標を行列で変換ということですね、なるほどです。Graphics系や行列の知識が整理できました。ありがとうございます。
    スキュー変換行列は傾ける基準点が中央なのとn倍で考える必要がありそうなので、今回は右側にある頂点をずらすという考え方でやってみます。
    for (var i = 0; i < vertexCount; i++) {
    var v = this.vertices [i];
    if (0 < v.position.x) {
    v.position = new Vector3 (v.position.x, v.position.y + 50, v.position.z);
    }
    this.vertices [i] = v;
    }

    キャンセル

  • 2018/06/03 13:54 編集

    傾けるほど枠の線が細くなり違和感が出るのが課題ではあります
    https://drive.google.com/open?id=18VX4RjqEd3O5O0zPHAhuiqshdN49nbcO

    キャンセル

  • 2018/06/05 16:48

    追記を参考に組み直したところ、綺麗に表示することができました!本当にありがとうございます!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.75%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C#

    6552questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

  • Unity

    3576questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

  • Unity3D

    1175questions

    Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

  • Unity2D

    758questions