FishPlayer

一个喜欢摸鱼的废物

0%

根据一个UI物体的位置摆放另一个UI物体

最近在遇到了一个需求,要点击某个道具UI之后,显示一个小的关于该道具的信息框。
大概是入下图这个样子的。

还好之前做过,于是乎直接修修补补腾过去。至于为什么要修补,是因为在之前的项目里虽然写了一些和RectTransform相关的一些utils,但都因为懒和菜,没有去维护。有重复的方法还有错误的方法,麻了。
这次说是修补,也稍微重写了一些和RectTransform相关的一些utils,也准备带进项目里看看有没有地方可以用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379

using UnityEngine;
using UnityEngine.UI;

namespace Temp
{
public enum RectPositionType
{
Center = 0,

Top = 1,
Bottom = 2,
Left = 3,
Right = 4,

TopLeft = 5,
TopRight = 6,

BottomLeft = 7,
BottomRight = 8,
}

public class UIElementPositionSetter : MonoBehaviour
{
[Header("Target setting")]
[SerializeField]
private RectPositionType _targetRectPosition = RectPositionType.Center;
[SerializeField]
private Vector2 _targetPivot = Vector2.zero;
[SerializeField]
private Vector2 _extraOffset = Vector2.zero;
[SerializeField]
private RectOffset _adaptionOffset;
[SerializeField]
private bool _doScreenAdaption;

[Header("Other setting")]
[SerializeField]
private RectTransform _defaultBoundsCanvasTransform;
[SerializeField]
private bool _adjustHorizontal = true;
[SerializeField]
private bool _adjustVertical = true;

public void SetPosition(RectTransform target)
{
RectTransform self = transform as RectTransform;
Vector3 spawnPos = GetLocalRectPosition(self, _targetRectPosition);
spawnPos += new Vector3(_extraOffset.x, _extraOffset.y, 0f);
spawnPos = self.localToWorldMatrix.MultiplyPoint(spawnPos);
target.pivot = _targetPivot;
target.position = spawnPos;
if (_doScreenAdaption)
{
// HACK @Hiko force rebuild to make sure the size is correct
LayoutRebuilder.ForceRebuildLayoutImmediate(target);
DoAdaption(target);
}
}

public void DoAdaption(RectTransform targetTransform)
{
if (_adjustHorizontal)
{
AdjustHorizontal(targetTransform, _defaultBoundsCanvasTransform);
}
if (_adjustVertical)
{
AdjustVertical(targetTransform, _defaultBoundsCanvasTransform);
}
}

public void AdjustHorizontal(RectTransform target, RectTransform outterBoundRectTransform)
{
Vector2 targetSize = target.rect.size;
Matrix4x4 targetLocalToWorld = target.localToWorldMatrix;
Matrix4x4 boundRectWorldToLocal = outterBoundRectTransform.worldToLocalMatrix;
Vector2 topLeftPos = GetLocalRectPosition(target, RectPositionType.TopLeft);

Vector3 checkWorldPoint = targetLocalToWorld.MultiplyPoint(topLeftPos + Vector2.down * targetSize.y * 0.5f + Vector2.left * _adaptionOffset.left);
Vector2 leftCheckLocalPoint = boundRectWorldToLocal.MultiplyPoint(checkWorldPoint);
Vector2 leftCheckBoundPoint = GetLocalRectPosition(outterBoundRectTransform, RectPositionType.Left);
bool leftOut = leftCheckLocalPoint.x < leftCheckBoundPoint.x;

checkWorldPoint = targetLocalToWorld.MultiplyPoint(topLeftPos + Vector2.down * targetSize.y * 0.5f + Vector2.right * targetSize.x + Vector2.right * _adaptionOffset.right);
Vector2 rightCheckLocalPoint = boundRectWorldToLocal.MultiplyPoint(checkWorldPoint);
Vector2 rightCheckBoundPoint = GetLocalRectPosition(outterBoundRectTransform, RectPositionType.Right);
bool rightOut = rightCheckLocalPoint.x > rightCheckBoundPoint.x;

if (leftOut && rightOut)
{
// outter bound is too small
return;
}

if (leftOut)
{
float xDelta = leftCheckLocalPoint.x - leftCheckBoundPoint.x;
Vector3 tempPos = target.localPosition;
tempPos.x -= xDelta;
target.localPosition = tempPos;
}
if (rightOut)
{
float xDelta = rightCheckLocalPoint.x - rightCheckBoundPoint.x;
Vector3 tempPos = target.localPosition;
tempPos.x -= xDelta;
target.localPosition = tempPos;
}
if (leftOut || rightOut)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(target);
}
}

public void AdjustVertical(RectTransform target, RectTransform outterBoundRectTransform)
{
Vector2 targetSize = target.rect.size;
Matrix4x4 targetLocalToWorld = target.localToWorldMatrix;
Matrix4x4 boundRectWorldToLocal = outterBoundRectTransform.worldToLocalMatrix;
Vector2 topLeftPos = GetLocalRectPosition(target, RectPositionType.TopLeft);

Vector3 checkWorldPoint = targetLocalToWorld.MultiplyPoint(topLeftPos + Vector2.right * targetSize.x * 0.5f + Vector2.up * _adaptionOffset.top);
Vector2 topCheckLocalPoint = boundRectWorldToLocal.MultiplyPoint(checkWorldPoint);
Vector2 topCheckBoundPoint = GetLocalRectPosition(outterBoundRectTransform, RectPositionType.Top);
bool topOut = topCheckLocalPoint.y > topCheckBoundPoint.y;

checkWorldPoint = targetLocalToWorld.MultiplyPoint(topLeftPos + Vector2.down * targetSize.y + Vector2.right * targetSize.x * 0.5f + Vector2.down * _adaptionOffset.bottom);
Vector2 bottomCheckLocalPoint = boundRectWorldToLocal.MultiplyPoint(checkWorldPoint);
Vector2 bottomCheckBoundPoint = GetLocalRectPosition(outterBoundRectTransform, RectPositionType.Bottom);
bool bottomOut = bottomCheckLocalPoint.y < bottomCheckBoundPoint.y;

if (topOut && bottomOut)
{
// outter bound is too small
return;
}

if (topOut)
{
float yDelta = topCheckLocalPoint.y - topCheckBoundPoint.y;
Vector3 tempPos = target.localPosition;
tempPos.y -= yDelta;
target.localPosition = tempPos;
}
if (bottomOut)
{
float yDelta = bottomCheckLocalPoint.y - bottomCheckBoundPoint.y;
Vector3 tempPos = target.localPosition;
tempPos.y -= yDelta;
target.localPosition = tempPos;
}
if (topOut || bottomOut)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(target);
}
}

#region position util

public static Vector2 GetLocalRectPosition(RectTransform target, RectPositionType offsetType)
{
return GetLocalRectPosition(target, offsetType, Vector2.zero);
}

public static Vector2 GetLocalRectPosition(RectTransform target, RectPositionType offsetType, Vector2 offset)
{
return GetLocalRectPosition(target.rect.size, target.pivot, offsetType, offset);
}

public static Vector2 GetLocalRectPosition(Vector2 targetSize, Vector2 targetPivot, RectPositionType offsetType, Vector2 offset)
{
Vector2 bottomLeft = Vector2.zero;
bottomLeft.x -= targetPivot.x * targetSize.x;
bottomLeft.y -= targetPivot.y * targetSize.y;
Vector2 result = bottomLeft;
switch (offsetType)
{
case RectPositionType.Top:
result += Vector2.right * 0.5f * targetSize.x + Vector2.up * targetSize.y;
break;
case RectPositionType.Bottom:
result += Vector2.right * 0.5f * targetSize.x;
break;
case RectPositionType.Left:
result += Vector2.up * 0.5f * targetSize.y;
break;
case RectPositionType.Right:
result += Vector2.right * targetSize.x + Vector2.up * 0.5f * targetSize.y;
break;
case RectPositionType.TopLeft:
result += Vector2.up * targetSize.y;
break;
case RectPositionType.TopRight:
result += Vector2.right * targetSize.x + Vector2.up * targetSize.y;
break;
case RectPositionType.BottomRight:
result += Vector2.right * targetSize.x;
break;
case RectPositionType.BottomLeft:
default:
break;
}
return result + offset;
}

public static Vector3 RectPositionToWorld(RectTransform rect, RectPositionType offsetType)
{
return RectPositionToWorld(rect, offsetType, Vector2.zero);
}

public static Vector3 RectPositionToWorld(RectTransform rect, RectPositionType offsetType, Vector2 offset)
{
var localPosition = GetLocalRectPosition(rect, offsetType, offset);
return rect.TransformPoint(localPosition);
}

private Vector2 GetTargetRectPositionInLocalReference(RectPositionType offsetType)
{
RectTransform self = transform as RectTransform;
Vector2 targetPivotPos = GetLocalRectPosition(self, _targetRectPosition, _extraOffset);
Vector2 virtualCenterPos = targetPivotPos;
virtualCenterPos += GetLocalRectPosition(_targetSize, _targetPivot, offsetType, Vector2.zero);
return virtualCenterPos;
}

#endregion

#if UNITY_EDITOR

[Header("Preview setting")]
[SerializeField]
private Vector2 _targetSize = Vector2.one * 128f;
[SerializeField]
private Color _previewColor = Color.white;
[SerializeField]
private bool _doDrawAdaptionOffsetRect = false;

[System.Serializable]
private struct TempCheckResult
{
public bool previewLeftOut;
public bool previewRightOut;
public bool previewTopOut;
public bool previewBottomOut;
}

[SerializeField]
private TempCheckResult _checkResult;

private bool _previewLeftOut;
private bool _previewRightOut;
private bool _previewTopOut;
private bool _previewBottomOut;

private void OnDrawGizmosSelected()
{
// draw A box to show the position of target
RectTransform self = transform as RectTransform;
Vector2 localSpawnPos = GetLocalRectPosition(self, _targetRectPosition);
localSpawnPos += _extraOffset;

Vector2 pivot = _targetPivot;
Vector2 topLeftPos = localSpawnPos;
topLeftPos.x -= pivot.x * _targetSize.x;
topLeftPos.y -= (pivot.y - 1f) * _targetSize.y;

if (_doScreenAdaption)
{
// do adaption
if (_defaultBoundsCanvasTransform != null) // if there is no bound, we can take the screen as the bound
{
Matrix4x4 localToWorld = self.localToWorldMatrix;
RectTransform outterBoundRectTransform = _defaultBoundsCanvasTransform;
Matrix4x4 boundRectWorldToLocal = outterBoundRectTransform.worldToLocalMatrix;

Vector3 checkWorldPoint = localToWorld.MultiplyPoint(topLeftPos + Vector2.down * _targetSize.y * 0.5f + Vector2.left * _adaptionOffset.left);
Vector2 leftCheckLocalPoint = boundRectWorldToLocal.MultiplyPoint(checkWorldPoint);
Vector2 leftCheckBoundPoint = GetLocalRectPosition(outterBoundRectTransform, RectPositionType.Left);
_previewLeftOut = leftCheckLocalPoint.x < leftCheckBoundPoint.x;

checkWorldPoint = localToWorld.MultiplyPoint(topLeftPos + Vector2.down * _targetSize.y * 0.5f + Vector2.right * _targetSize.x + Vector2.right * _adaptionOffset.right);
Vector2 rightCheckLocalPoint = boundRectWorldToLocal.MultiplyPoint(checkWorldPoint);
Vector2 rightCheckBoundPoint = GetLocalRectPosition(outterBoundRectTransform, RectPositionType.Right);
_previewRightOut = rightCheckLocalPoint.x > rightCheckBoundPoint.x;

checkWorldPoint = localToWorld.MultiplyPoint(topLeftPos + Vector2.right * _targetSize.x * 0.5f + Vector2.up * _adaptionOffset.top);
Vector2 topCheckLocalPoint = boundRectWorldToLocal.MultiplyPoint(checkWorldPoint);
Vector2 topCheckBoundPoint = GetLocalRectPosition(outterBoundRectTransform, RectPositionType.Top);
_previewTopOut = topCheckLocalPoint.y > topCheckBoundPoint.y;

checkWorldPoint = localToWorld.MultiplyPoint(topLeftPos + Vector2.down * _targetSize.y + Vector2.right * _targetSize.x * 0.5f + Vector2.down * _adaptionOffset.bottom);
Vector2 bottomCheckLocalPoint = boundRectWorldToLocal.MultiplyPoint(checkWorldPoint);
Vector2 bottomCheckBoundPoint = GetLocalRectPosition(outterBoundRectTransform, RectPositionType.Bottom);
_previewBottomOut = bottomCheckLocalPoint.y < bottomCheckBoundPoint.y;

if (_adjustHorizontal)
{
if (_previewLeftOut && _previewRightOut)
{
// bound too small
}
else
{
if (_previewLeftOut)
{
float xDelta = leftCheckLocalPoint.x - leftCheckBoundPoint.x;
Vector3 tempPos = topLeftPos;
tempPos.x -= xDelta;
topLeftPos = tempPos;
}
if (_previewRightOut)
{
float xDelta = rightCheckLocalPoint.x - rightCheckBoundPoint.x;
Vector3 tempPos = topLeftPos;
tempPos.x -= xDelta;
topLeftPos = tempPos;
}
}
}

if (_adjustVertical)
{
if (_previewTopOut && _previewBottomOut)
{
// bound too small
}
else
{
if (_previewTopOut)
{
float yDelta = topCheckLocalPoint.y - topCheckBoundPoint.y;
Vector3 tempPos = topLeftPos;
tempPos.y -= yDelta;
topLeftPos = tempPos;
}
if (_previewBottomOut)
{
float yDelta = bottomCheckLocalPoint.y - bottomCheckBoundPoint.y;
Vector3 tempPos = topLeftPos;
tempPos.y -= yDelta;
topLeftPos = tempPos;
}
}
}
}
}

_checkResult.previewLeftOut = _previewLeftOut;
_checkResult.previewRightOut = _previewRightOut;
_checkResult.previewTopOut = _previewTopOut;
_checkResult.previewBottomOut = _previewBottomOut;

if (_doDrawAdaptionOffsetRect && _defaultBoundsCanvasTransform != null)
{
Vector2 innerBoundLeftPos = GetLocalRectPosition(_defaultBoundsCanvasTransform, RectPositionType.TopLeft, new Vector2(_adaptionOffset.left, -_adaptionOffset.top));
Vector2 size = _defaultBoundsCanvasTransform.rect.size;
size.x -= _adaptionOffset.horizontal;
size.y -= _adaptionOffset.vertical;
DrawBox(_defaultBoundsCanvasTransform, innerBoundLeftPos, size, Color.blue);
}
DrawBox(self, topLeftPos, _targetSize, _previewColor);
}

private void DrawBox(RectTransform reference, Vector2 topLeftPos, Vector2 size, Color color)
{
Color cachedColor = Gizmos.color;
Gizmos.color = color;
Gizmos.DrawLine(reference.TransformPoint(topLeftPos), reference.TransformPoint(topLeftPos + Vector2.right * size.x));
Gizmos.DrawLine(reference.TransformPoint(topLeftPos), reference.TransformPoint(topLeftPos + Vector2.down * size.y));
Gizmos.DrawLine(reference.TransformPoint(topLeftPos + Vector2.right * size.x), reference.TransformPoint(topLeftPos + Vector2.right * size.x + Vector2.down * size.y));
Gizmos.DrawLine(reference.TransformPoint(topLeftPos + Vector2.down * size.y), reference.TransformPoint(topLeftPos + Vector2.right * size.x + Vector2.down * size.y));
Gizmos.color = cachedColor;
}

#endif

}
}

还是像以前一样,先在debug draw里把功能完成再给移到实际的物体上。
但这次还是很可惜没有把功能很好抽出成比较单独的数学运算,而且感觉要用到的参数好多也没法拆分,最后留了一个多参的入口。