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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C#

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

Unity3D

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

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

3119閲覧

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

proelisoft

総合スコア8

C#

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

Unity3D

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

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

1グッド

0クリップ

投稿2018/06/02 09:28

実現したいこと

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

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

試したこと

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

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

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

補足情報

Unity2017 3.0f3

Hawn👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

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

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

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

C#

1using System.Collections.Generic; 2using System.Reflection; 3using UnityEngine; 4using UnityEngine.UI; 5 6public class SkewedImage : Image 7{ 8 public Vector2 Skew; // 水平・垂直スキューのタンジェント...たとえば(0, 1)とすると、X成分の増加に傾き1で比例してYも増加する→X軸が45°傾く 9 10 protected override void OnPopulateMesh(VertexHelper toFill) 11 { 12 // 本来のImageの頂点を作成させる 13 base.OnPopulateMesh(toFill); 14 15 // Imageが作成した頂点を(むりやり)取得 16 var positionsInfo = typeof(VertexHelper).GetField( 17 "m_Positions", 18 BindingFlags.Instance | BindingFlags.NonPublic); 19 var positions = positionsInfo.GetValue(toFill) as List<Vector3>; 20 var positionCount = positions == null ? 0 : positions.Count; 21 22 // スキュー変換行列を作成 23 var skewMatrix = Matrix4x4.identity; 24 skewMatrix.m01 = this.Skew.x; 25 skewMatrix.m10 = this.Skew.y; 26 27 // 全頂点にスキュー変換を適用 28 for (var i = 0; i < positionCount; i++) 29 { 30 positions[i] = skewMatrix.MultiplyPoint(positions[i]); 31 } 32 } 33}

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

C#

1using UnityEditor; 2using UnityEditor.UI; 3 4[CustomEditor(typeof(SkewedImage), true)] 5[CanEditMultipleObjects] 6public class SkewedImageEditor : ImageEditor 7{ 8 public override void OnInspectorGUI() 9 { 10 base.OnInspectorGUI(); // まず本来のImage用エディターを表示する 11 var targetSkewedImage = this.target as SkewedImage; 12 if (targetSkewedImage != null) 13 { 14 var prevSkew = targetSkewedImage.Skew; // 編集前のスキュー 15 var newSkew = EditorGUILayout.Vector2Field("Skew", prevSkew); // 編集後のスキュー 16 if (newSkew != prevSkew) // 値の編集が行われたならば... 17 { 18 targetSkewedImage.Skew = newSkew; // スキューを新しい値に更新し... 19 targetSkewedImage.SetVerticesDirty(); // メッシュの再生成が必要なことを知らせる 20 } 21 } 22 } 23}

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

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

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

追記

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

C#

1using System.Collections.Generic; 2using UnityEngine; 3using UnityEngine.UI; 4 5public class SkewedImage : Image 6{ 7 public Vector2 Skew; // 水平・垂直スキューのタンジェント...たとえば(0, 1)とすると、X成分の増加に傾き1で比例してYも増加する→X軸が45°傾く 8 9 private List<UIVertex> vertices; 10 11 protected override void OnPopulateMesh(VertexHelper toFill) 12 { 13 // 本来のImageの頂点を作成させる 14 base.OnPopulateMesh(toFill); 15 16 // Imageが作成した頂点をまともな方法で取得 17 // どうやらメッシュがインデックス付きからインデックスなしに変わってしまう? 18 // UI用オブジェクトなら、インデックスなしメッシュでもさほど頂点数は増えないだろうと期待する 19 if (this.vertices == null) 20 { 21 this.vertices = new List<UIVertex>(); 22 } 23 toFill.GetUIVertexStream(this.vertices); 24 var vertexCount = this.vertices.Count; 25 26 // スキュー変換行列を作成 27 var skewMatrix = Matrix4x4.identity; 28 skewMatrix.m01 = this.Skew.x; 29 skewMatrix.m10 = this.Skew.y; 30 31 // スキュー変換適用後の頂点でメッシュを置き換える 32 // せっかくImageが作ったメッシュを破棄してしまっている気がするが、こちらの方が正当なやり方かもしれない 33 for (var i = 0; i < vertexCount; i++) 34 { 35 var v = this.vertices[i]; 36 v.position = skewMatrix.MultiplyPoint3x4(v.position); // 平行四辺形変形ならMultiplyPoint3x4で対応可能なので、こっちを使った方が少し高速? 37 this.vertices[i] = v; 38 } 39 toFill.Clear(); 40 toFill.AddUIVertexTriangleStream(this.vertices); 41 } 42}

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

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

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

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

SkewedImage改変版

C#

1using System.Collections.Generic; 2using UnityEngine; 3using UnityEngine.UI; 4 5public class SkewedImage : Image 6{ 7 // スライス区切り位置に相当する頂点のインデックスの表 8 private static readonly int[] SliceOffsetV0Indices = 9 { 10 1, 2, 3, 6, 10, 11, 11 19, 20, 21, 24, 28, 29, 12 37, 38, 39, 42, 46, 47 13 }; 14 private static readonly int[] SliceOffsetV1Indices = 15 { 16 7, 8, 9, 12, 16, 17, 17 25, 26, 27, 30, 34, 35, 18 43, 44, 45, 48, 52, 53 19 }; 20 21 public Vector2 Skew; 22 23 // スライス区切り座標のずらし量 24 public Vector2 SliceOffsetV; 25 26 private List<UIVertex> vertices; 27 28 protected override void OnPopulateMesh(VertexHelper toFill) 29 { 30 base.OnPopulateMesh(toFill); 31 if (this.vertices == null) 32 { 33 this.vertices = new List<UIVertex>(); 34 } 35 toFill.GetUIVertexStream(this.vertices); 36 var vertexCount = this.vertices.Count; 37 var skewMatrix = Matrix4x4.identity; 38 skewMatrix.m01 = this.Skew.x; 39 skewMatrix.m10 = this.Skew.y; 40 41 // スライス区切りインデックス表を参照するためのインデックス 42 var sV0 = 0; 43 var sV1 = 0; 44 45 for (var i = 0; i < vertexCount; i++) 46 { 47 var v = this.vertices[i]; 48 v.position = skewMatrix.MultiplyPoint3x4(v.position); 49 50 // iがスライス区切りインデックス表に載っていれば、Y座標をずらす 51 if (i == SliceOffsetV0Indices[sV0]) 52 { 53 // 第1のスライス区切り位置をずらす 54 v.position.y += this.SliceOffsetV.x; 55 sV0 = Mathf.Min(sV0 + 1, SliceOffsetV0Indices.Length - 1); 56 } 57 else if (i == SliceOffsetV1Indices[sV1]) 58 { 59 // 第2のスライス区切り位置をずらす 60 v.position.y += this.SliceOffsetV.y; 61 sV1 = Mathf.Min(sV1 + 1, SliceOffsetV1Indices.Length - 1); 62 } 63 64 this.vertices[i] = v; 65 } 66 67 toFill.Clear(); 68 toFill.AddUIVertexTriangleStream(this.vertices); 69 } 70}

SkewedImageEditor改変版

C#

1using UnityEditor; 2using UnityEditor.UI; 3using UnityEngine; 4 5[CustomEditor(typeof(SkewedImage), true)] 6[CanEditMultipleObjects] 7public class SkewedImageEditor : ImageEditor 8{ 9 public override void OnInspectorGUI() 10 { 11 base.OnInspectorGUI(); 12 var targetSkewedImage = this.target as SkewedImage; 13 if (targetSkewedImage != null) 14 { 15 var prevSkew = targetSkewedImage.Skew; 16 var newSkew = EditorGUILayout.Vector2Field("Skew", prevSkew); 17 var dirty = false; 18 if (newSkew != prevSkew) 19 { 20 targetSkewedImage.Skew = newSkew; 21 dirty = true; 22 } 23 var prevSliceOffsetV = targetSkewedImage.SliceOffsetV; 24 var newSliceOffsetV = EditorGUILayout.Vector2Field("Vertical Slice Offset", prevSliceOffsetV); 25 if (newSliceOffsetV != prevSliceOffsetV) 26 { 27 targetSkewedImage.SliceOffsetV = newSliceOffsetV; 28 dirty = true; 29 } 30 if (dirty) 31 { 32 targetSkewedImage.SetVerticesDirty(); 33 } 34 } 35 } 36}

動かした様子
プレビュー

投稿2018/06/02 14:48

編集2018/06/03 22:01
Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

proelisoft

2018/06/03 04: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; }
proelisoft

2018/06/05 07:48

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問