確かにご質問者さんがなさっているように色を配列として保存しておくというのも一案だとは思いますが、もっと手軽にInstantiate でテクスチャ自体を複製してやるのはどうでしょうかね?
試しにPainter
を下記のように改造してみました。すみませんが思考の整理のため変数名やら処理フローやらをいろいろといじってしまったので、オリジナルの「UI.Image にペイントする。線を手書きする。 」のスクリプトとは様相が変わってしまいました。ですが「テクスチャのピクセルを1つずつ塗っていく」というアプローチはそのままです。
※結局LineRenderer
じゃなくてUndo・Redoに関する回答になってしまいすみません...
まだご質問の文面通りLineRenderer
に関する回答をお待ちでしたらいいのですが、もしUndo・Redoにテーマを変更するおつもりでしたら、お時間のある際にでもご質問のタイトルや文面に「当初はこういう内容だったが、こういう方針に変更した」みたいなことがわかるような修正を加えていただくと、他の閲覧者の方々にもわかりやすいかもしれません。
C#
1 using System ;
2 using System . Collections . Generic ;
3 using System . Linq ;
4 using UnityEngine ;
5 using UnityEngine . UI ;
6
7 /// <summary>
8 /// お絵描き
9 /// </summary>
10 [ RequireComponent ( typeof ( RectTransform ) , typeof ( Image ) ) ]
11 public 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 }