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

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

新規登録して質問してみよう
ただいま回答率
85.47%
Unity

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

Q&A

解決済

1回答

5437閲覧

UNITY LineRendererで線を描く事はできるのですが,canvasの下のimageに描く事は出来ないでしょうか?

29507-01

総合スコア46

Unity

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

0グッド

1クリップ

投稿2020/01/01 22:51

編集2020/01/01 22:52

前提・実現したいこと

LineRendererで線を描く事はできるのですが,canvasの下のimageに描く事は出来ないでしょうか?
LineRendererでは無理なのでしょうか?

イメージ説明

該当のソースコード

Unity

1lineRendererMultipleList.Last().Last().positionCount++; 2positionIndex=lineRendererMultipleList.Last().Last().positionCount; 3lineRendererMultipleList.Last().Last().SetPosition(positionIndex - 1, mousePosition);

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

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

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

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

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

Bongo

2020/01/02 21:50 編集

以前の「Unity canvasに描いた線を配列に保存してUndo,Redoしたい」(https://teratail.com/questions/232188 )とのご質問を拝見しますに、すでにImage上のテクスチャを書き換えることによって線を引く方法は試されたご様子ですが、これとは違うということでしょうか(テクスチャ書き換えだと何らかの理由で行き詰まってしまい、LineRendererによる方法に変更した...とか)? LineRendererの線がImageに隠れてしまって困るということでしたら、CanvasのRender ModeをScreen Space - Cameraにして、Plane Distanceを大きめにしてシーン空間内のオブジェクトよりも遠くにUI類が描画されるようにしてやる手が使えるかもしれません。 他にも、おととしの話になってしまいますが、別の方の「Unityで線を描画したい」(https://teratail.com/questions/109826 )とのご質問の中で「UIオブジェクトとして線を作成する」というのを提案させていただいたことがあります。こういった方法だとご質問者さんの目的にはそぐわない感じでしょうか?
29507-01

2020/01/02 22:51

おっしゃる通り,以前(https://teratail.com/questions/230866) で投稿させていただきimageに線を描く事は出来たのですが,線を太くするとかUndo,Redoの方法が分からなくRineRendererだと実現可能なのですが今度はImage上に描く方法が分からなくなってしまいました。 線を描く public void DoUndo() { if (undoEnabled) { if (undoPixels.Count > 0) { System.Array.Copy(undoPixels[undoPixels.Count - 1 ], pixels, undoPixels[undoPixels.Count - 1 ].Length); texture.LoadRawTextureData(undoPixels[undoPixels.Count - 1]); if (debugMode) Debug.Log("(del)undoPixels.Count = " + (undoPixels.Count - 1)); texture.Apply(); undoPixels.RemoveAt(undoPixels.Count - 1); } } } セット方法は undoPixels.Add(new byte[texWidth * texHeight * 4]); // TODO: need to reset size, if image size changes System.Array.Copy(pixels, undoPixels[undoPixels.Count - 1], pixels.Length);
29507-01

2020/01/02 23:25 編集

if (Input.GetMouseButton(1)) { undoEnabled = true; DoUndo(); undoEnabled = false; } この処理を追加したのですが,Updateで何回もこの処理がLoopされ最初の線のみ残り消えました。 しかしボタンを一つ追加してみたのですが,消えません。
29507-01

2020/01/02 23:56 編集

内容がごちゃまぜになりました。 Space - Cameraにしたのですが,Planeを教えて頂けませんか?
Bongo

2020/01/03 11:46

お返事が遅くなりました。「Render Camera」欄にシーン上のカメラをセットすると、その下に「Plane Distance」の項目が現れるかと思います。 念のため申し上げますと、LineRendererを使う方針で行くとなるとImageをシーン内に配置する意義はあまりないんじゃないかと思います。LineRendererは線の形をした3Dのオブジェクトを置いているようなものですから、単にLineRendererの背後にImageを置いただけではImage上の画像として線が描き込まれることはなく、Imageはただの背景として扱われるでしょう(本当にImage上の画像にLineRendererの描画結果を反映させることもできるだろうとは思いますが、SetPixelsによるテクスチャ書き込み方式と同等かそれ以上に説明がややこしくなりそうです...)。
29507-01

2020/01/04 06:50 編集

Imageの上に線が描画できればScreenShotでCanvasを切り抜き保存しようと考えていましたが上手くいかないようです。
guest

回答1

0

ベストアンサー

確かにご質問者さんがなさっているように色を配列として保存しておくというのも一案だとは思いますが、もっと手軽にInstantiateでテクスチャ自体を複製してやるのはどうでしょうかね?
試しにPainterを下記のように改造してみました。すみませんが思考の整理のため変数名やら処理フローやらをいろいろといじってしまったので、オリジナルの「UI.Image にペイントする。線を手書きする。」のスクリプトとは様相が変わってしまいました。ですが「テクスチャのピクセルを1つずつ塗っていく」というアプローチはそのままです。

※結局LineRendererじゃなくてUndo・Redoに関する回答になってしまいすみません...
まだご質問の文面通りLineRendererに関する回答をお待ちでしたらいいのですが、もしUndo・Redoにテーマを変更するおつもりでしたら、お時間のある際にでもご質問のタイトルや文面に「当初はこういう内容だったが、こういう方針に変更した」みたいなことがわかるような修正を加えていただくと、他の閲覧者の方々にもわかりやすいかもしれません。

C#

1using System; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEngine; 5using UnityEngine.UI; 6 7/// <summary> 8/// お絵描き 9/// </summary> 10[RequireComponent(typeof(RectTransform), typeof(Image))] 11public class Painter : MonoBehaviour 12{ 13 // ペンの半径...0で1ピクセルの線になる 14 [Range(0, 5)] public int penRadius; 15 16 // 前景色 17 public Color foregroundColor = Color.red; 18 19 // 背景色 20 [SerializeField] private Color backgroundColor = Color.white; 21 22 private RectTransform rectTransform; 23 private Image image; 24 private Texture2D texture; 25 private Vector2Int textureSize; 26 private Vector2Int previousPixelPosition; 27 private Vector2Int cachedPixelPosition; 28 29 // Undo、Redo用のテクスチャ格納庫 30 private readonly Stack<Texture2D> undoStack = new Stack<Texture2D>(); 31 private readonly Stack<Texture2D> redoStack = new Stack<Texture2D>(); 32 33 // Undo、Redo可能かどうかを外部スクリプトから調べたい場合、これらプロパティを参照する 34 public bool CanUndo 35 { 36 get { return this.undoStack.Count > 0; } 37 } 38 39 public bool CanRedo 40 { 41 get { return this.redoStack.Count > 0; } 42 } 43 44 // Undo時にはundoStackからテクスチャを1つ取り出して現在のテクスチャとし、 45 // 古いテクスチャはredoStackに積んでおく 46 public void Undo() 47 { 48 if (!this.CanUndo) 49 { 50 return; 51 } 52 53 var previousTexture = this.texture; 54 var poppedTexture = this.undoStack.Pop(); 55 this.texture = poppedTexture; 56 this.redoStack.Push(previousTexture); 57 this.UpdateSprite(); 58 Debug.LogFormat("Undo! UndoStack : {0} RedoStack : {1}", this.undoStack.Count, this.redoStack.Count); 59 } 60 61 // Redo時にはredoStackからテクスチャを1つ取り出して現在のテクスチャとし、 62 // 古いテクスチャはundoStackに積んでおく 63 public void Redo() 64 { 65 if (!this.CanRedo) 66 { 67 return; 68 } 69 70 var previousTexture = this.texture; 71 var poppedTexture = this.redoStack.Pop(); 72 this.texture = poppedTexture; 73 this.undoStack.Push(previousTexture); 74 this.UpdateSprite(); 75 Debug.LogFormat("Redo! UndoStack : {0} RedoStack : {1}", this.undoStack.Count, this.redoStack.Count); 76 } 77 78 // テクスチャに何かしらの操作(線を引くなど)を行う前にはRecordを実行することにする 79 // これによって変更前のテクスチャが複製されundoStackに積まれる 80 // また、このタイミングでredoStackは空にする 81 private void Record() 82 { 83 this.undoStack.Push(Instantiate(this.texture)); 84 foreach (var textureInRedoStack in this.redoStack) 85 { 86 Destroy(textureInRedoStack); 87 } 88 89 this.redoStack.Clear(); 90 Debug.LogFormat("Record! UndoStack : {0} RedoStack : {1}", this.undoStack.Count, this.redoStack.Count); 91 } 92 93 private void UpdateSprite() 94 { 95 var previousSprite = this.image.sprite; 96 this.image.sprite = Sprite.Create( 97 this.texture, 98 new Rect(0, 0, this.texture.width, this.texture.height), 99 Vector2.zero); 100 Destroy(previousSprite); 101 } 102 103 private Vector2Int GetPixelPosition() 104 { 105 if (!RectTransformUtility.ScreenPointToLocalPointInRectangle( 106 this.rectTransform, 107 Input.mousePosition, 108 null, 109 out var pixelPositionFloat)) 110 { 111 return this.cachedPixelPosition; 112 } 113 114 var sizeDelta = this.rectTransform.sizeDelta; 115 pixelPositionFloat += sizeDelta * this.rectTransform.pivot; 116 var pixelPosition = new Vector2Int( 117 Mathf.Clamp((int)pixelPositionFloat.x, 0, this.textureSize.x - 1), 118 Mathf.Clamp((int)pixelPositionFloat.y, 0, this.textureSize.y - 1)); 119 Debug.LogFormat("Pixel Position: {0}", pixelPosition); 120 this.cachedPixelPosition = pixelPosition; 121 return this.cachedPixelPosition; 122 } 123 124 // 入力座標がテクスチャ範囲内ならtrue、さもなければfalse 125 private bool PositionIsWithinTexture(Vector2Int position) 126 { 127 return (position.x >= 0) && (position.y >= 0) && 128 (position.x < this.textureSize.x) && (position.y < this.textureSize.y); 129 } 130 131 // 入力座標に円を描く 132 private void PaintAt(Vector2Int position, Color32 color, int radius) 133 { 134 var sqrRadius = radius * radius; 135 for (var i = -radius; i <= radius; i++) 136 { 137 for (var j = -radius; j <= radius; j++) 138 { 139 var p = new Vector2Int(position.x + i, position.y + j); 140 var dot = (i * i) + (j * j); 141 if ((dot <= sqrRadius) && this.PositionIsWithinTexture(p)) 142 { 143 this.texture.SetPixel(p.x, p.y, color); 144 } 145 } 146 } 147 148 this.texture.Apply(); 149 } 150 151 private void LineTo(Vector2Int from, Vector2Int to, Color32 color, int radius) 152 { 153 var penPosition = from; 154 var x = (float)penPosition.x; 155 var y = (float)penPosition.y; 156 var fromTo = to - from; 157 var fromToSign = new Vector2Int((int)Mathf.Sign(fromTo.x), (int)Mathf.Sign(fromTo.y)); 158 var fromToXIsZero = fromToSign.x == 0; 159 var fromToYIsZero = fromToSign.y == 0; 160 fromToSign.x = fromToXIsZero ? 1 : fromToSign.x; 161 fromToSign.y = fromToYIsZero ? 1 : fromToSign.y; 162 var absFromTo = fromToSign * fromTo; 163 var dominantDirection = 0; 164 var delta = Vector2.zero; 165 if (absFromTo.x >= absFromTo.y) 166 { 167 var tangent = fromToXIsZero ? 0 : (float)fromTo.y / fromTo.x; 168 delta.x = fromToSign.x; 169 delta.y = tangent * delta.x; 170 } 171 else 172 { 173 var tangent = fromToYIsZero ? 0 : (float)fromTo.x / fromTo.y; 174 delta.y = fromToSign.y; 175 delta.x = tangent * delta.y; 176 dominantDirection = 1; 177 } 178 179 while (this.PositionIsWithinTexture(penPosition)) 180 { 181 try 182 { 183 this.PaintAt(penPosition, color, radius); 184 x += delta.x; 185 y += delta.y; 186 penPosition.x = (int)x; 187 penPosition.y = (int)y; 188 if (fromToSign[dominantDirection] > 0) 189 { 190 if (penPosition[dominantDirection] >= to[dominantDirection]) 191 { 192 break; 193 } 194 } 195 else 196 { 197 if (penPosition[dominantDirection] <= to[dominantDirection]) 198 { 199 break; 200 } 201 } 202 } 203 catch (Exception e) 204 { 205 Debug.LogException(e); 206 break; 207 } 208 } 209 } 210 211 private void Start() 212 { 213 this.rectTransform = this.transform as RectTransform; 214 this.image = this.GetComponent<Image>(); 215 var sizeDelta = this.rectTransform.sizeDelta; 216 var width = (int)sizeDelta.x; 217 var height = (int)sizeDelta.y; 218 this.texture = new Texture2D(width, height, TextureFormat.ARGB32, false) {filterMode = FilterMode.Bilinear}; 219 this.texture.SetPixels32(Enumerable.Repeat<Color32>(this.backgroundColor, width * height).ToArray()); 220 this.texture.Apply(); 221 this.textureSize = new Vector2Int(width, height); 222 this.UpdateSprite(); 223 } 224 225 private void Update() 226 { 227 if (Input.GetKeyDown(KeyCode.U)) 228 { 229 this.Undo(); 230 } 231 232 if (Input.GetKeyDown(KeyCode.R)) 233 { 234 this.Redo(); 235 } 236 237 if (Input.GetMouseButtonDown(0)) 238 { 239 this.Record(); 240 this.previousPixelPosition = this.GetPixelPosition(); 241 } 242 else if (Input.GetMouseButton(0)) 243 { 244 var currentPixelPosition = this.GetPixelPosition(); 245 this.LineTo(this.previousPixelPosition, currentPixelPosition, this.foregroundColor, this.penRadius); 246 this.previousPixelPosition = currentPixelPosition; 247 } 248 } 249}

投稿2020/01/04 08:41

編集2020/01/05 00:58
Bongo

総合スコア10807

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

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

29507-01

2020/01/05 00:54 編集

public bool CanUndo => this.undoStack.Count > 0; public bool CanUndo => this.undoStack.Count > 0; この個所でc#4では使えません。と表示されエラーは解除してテストしているのですがこのまま使いたい場合私の環境では何がまずいのでしょうか? Visual Studio 2019を使っています。
Bongo

2020/01/05 00:59

コードを旧バージョンでも使えると思われる記法に変更してみました。これならどうでしょうか?
29507-01

2020/01/05 02:39 編集

有難うございます。 上手くいきました。 すみませんがお願いついでにImageの外でも塗りたいのと線のサイズ変更の仕方を教えて頂けませんか? 線のサイズ変更は [Range(0, 5)] public int penRadius; で出来ました。 out var pixelPositionFloat) - - - この個所でc#4では使えません。 と表示され  Vector2 pixelPositionFloat; out pixelPositionFloat)) と変更しましたが問題ないですよね。
Bongo

2020/01/05 03:25

おっとすみません、「out var」の箇所もありましたね。おっしゃるとおりそのように書き換えてしまって大丈夫です。線のサイズ変更についてもお試しのとおりpenRadiusを増減させればいいでしょう。 Imageの外でも塗りたい(つまりImageは単なる背景画像という扱いで、Imageの存在は無関係に画面全体に描画したいという意味でいいでしょうか?)となると、これはImageのテクスチャに対するSetPixelによる書き換えでは対応できなそうですね... そうなると今度はLineRendererによる線引きに方針を変更しないといけないかと思います。LineRendererによる方法はテクスチャ書き換えによる方法とはけっこう異なるアプローチでしょうから、現状のPainterのコードは捨てて作り直す必要があるでしょう。とはいえすでにこのご質問の投稿時点では、Undo機能はないかもしれませんがLineRendererによる太さ調整機能付きフリーハンド線引きはできているのですよね?となるとあとはUndo機能だけかと思います。今回の回答に提示しましたPainterでは過去のテクスチャを保存して以前の状態に戻れるようにしていますが、LineRendererを使った方法の場合は折れ線の頂点座標の配列を保存することになるでしょう。
29507-01

2020/01/05 03:48 編集

丁寧に有難うございました。 LineRendsrerはImageには線を引けないのですよね。 もう一つだけ良いですか ? Imageにxx.png画像をセットしてその上に塗りたいのですが? public Color foregroundColor ; にして デフォルトでImageにセットしてもダメなのです。
Bongo

2020/01/05 04:25

では、Startを... private void Start() { this.rectTransform = this.transform as RectTransform; this.image = this.GetComponent<Image>(); var sizeDelta = this.rectTransform.sizeDelta; var width = (int)sizeDelta.x; var height = (int)sizeDelta.y; var backgroundPixels = (this.image.mainTexture as Texture2D).GetPixels32(); this.image.sprite = null; this.texture = new Texture2D(width, height, TextureFormat.ARGB32, false) {filterMode = FilterMode.Bilinear}; this.texture.SetPixels32(backgroundPixels); this.texture.Apply(); this.textureSize = new Vector2Int(width, height); this.UpdateSprite(); } として、Imageに元々セットされているテクスチャから色の配列を取得し、それをお絵かき用テクスチャにセットしてみてはいかがでしょうか。ただし注意点として、元々のテクスチャのサイズはImageのサイズと同じであること(そうしないとピクセルの総数が合わず、SetPixels32に失敗します)と、元々のテクスチャのインポート設定の「Advanced」→「Read/Write Enabled」をオンにしておくこと(そうしないとGetPixels32でピクセルを読み取れません)が必要です。
29507-01

2020/01/05 04:36

本当に!! 丁寧に有難うございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.47%

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

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

質問する

関連した質問