XLua开发的一些问题记录

  1. C#枚举在lua中转Int:CS.System.Convert.ChangeType()
  2. 泛型函数使用:xlua.get_generic_method(instance, “func_name”); //成员函数传实例,静态函数传类型

RawImage使用扩展

前言

使用通用管理器UIModelManager,方便RawImage,RT加载卸载,以及RawImage与模型交互

基础思路

  1. 对应每一张RawImage,由于显示不同都需要不同的Camera以及RT,将Camera.targetTexture设置成RT
  2. 模型加载卸载走项目通用方式
  3. 交互问题:在UI上我们只能点击这个RawImage,如果RawImage中只显示一个模型,那我们可以直接在UI上监听拖拽修改这个模型的旋转或者其他操作
    但是这样做非常不友好,并不能精确的知道是否点中了这张RT上的模型,扩展一下改成世界坐标射线检测,这样不管RT上有多少模型都可以做交互
  4. 整理一下交互中射线检测计算思路:
    (1) 拿到点击位置计算其在RawImage中的偏移值
    (2) 将偏移值运算在相机的实际渲染高宽上
    (3) 怎么获得相机实际渲染高宽:
    正交相机:height=orthographicSize * 2, width = 宽高比height
    透视相机:height=tan(fov / 2) * near * 2, width = 宽高比
    height
    具体推算过程仔细看下这个公式就能知道,关于正交相机高度=size*2这个是Unity中的固定值
  5. 交互按需求自行添加,在UIRenderToTexture中监听IDragHandler, IBeginDragHandler, IEndDragHandler, IPointerClickHandler等即可
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
private void SetClickTarget(Vector2 clickPos)
{
// 通过射线检测当前点击的模型
// 拿到点击位置与UI位置的偏移百分比
Camera uiCamera = Hooks.CameraManager.UICamera;
float width = m_RawImage.rectTransform.rect.width;
float height = m_RawImage.rectTransform.rect.height;
Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, transform.position);
screenPos += new Vector2(width * (0.5f - m_RawImage.rectTransform.pivot.x), height * (0.5f - m_RawImage.rectTransform.pivot.y));
Vector2 offset = clickPos - screenPos;
offset = new Vector2(offset.x / width, offset.y / height);
if (m_Camera.orthographic)
{
// 正交相机的渲染大小height=size*2, width = 宽高比*height
float screenHeight = m_Camera.orthographicSize * 2;
float screenWidth = m_Camera.pixelWidth * 1.0f / m_Camera.pixelHeight * screenHeight;
Vector3 startPoint = m_Camera.transform.position + m_Camera.transform.right * offset.x * screenWidth + m_Camera.transform.up * offset.y * screenHeight;

Debug.DrawLine(startPoint, startPoint + m_Camera.transform.forward * 100, Color.red, 5f);
if (Physics.Raycast(startPoint, m_Camera.transform.forward, out RaycastHit hit, 100, 1 << Constant.Layer.UIRenderToTarget))
{
m_DragTarget = hit.transform;
}
}
else
{
// 透视相机通过FOV和near可以求height, width同正交相机
float screenHeight = Mathf.Tan(m_Camera.fieldOfView * 0.5f * Mathf.Deg2Rad) * m_Camera.nearClipPlane * 2;
float screenWidth = m_Camera.pixelWidth * 1.0f / m_Camera.pixelHeight * screenHeight;
Vector3 endPoint = m_Camera.transform.position + m_Camera.transform.forward * m_Camera.nearClipPlane
+ m_Camera.transform.right * offset.x * screenWidth + m_Camera.transform.up * offset.y * screenHeight;
Vector3 dir = (endPoint - m_Camera.transform.position).normalized;

Debug.DrawLine(m_Camera.transform.position, endPoint, Color.red, 5f);
if (Physics.Raycast(m_Camera.transform.position, dir, out RaycastHit hit, m_Camera.farClipPlane, 1 << Constant.Layer.UIRenderToTarget))
{
m_DragTarget = hit.transform;
}
}
}

代码

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
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace Gameplay
{
public class UIModelManager : ManagerBase
{
private Stack<Camera> m_CameraPool = new Stack<Camera>();
private Stack<int> m_IndexPool = new Stack<int>();
private int m_PoolCount = 0;

private Transform m_UIModelRoot;
private Light m_UIModelLight;

public override void OnInitilize()
{
base.OnInitilize();

m_UIModelRoot = new GameObject("UIModelRoot").transform;
m_UIModelRoot.SetParentEx(null);
GameObject.DontDestroyOnLoad(m_UIModelRoot);

m_UIModelLight = new GameObject("UIModelLight").GetOrAddComponent<Light>();
m_UIModelLight.transform.SetParentEx(m_UIModelRoot);

m_UIModelLight.transform.localEulerAngles = new Vector3(36, 30, 0);
m_UIModelLight.cookieSize = 10;
m_UIModelLight.type = LightType.Directional;
m_UIModelLight.cullingMask = 1 << Constant.Layer.UI | 1 << Constant.Layer.UIRenderToTarget;
m_UIModelLight.shadows = LightShadows.None;

m_UIModelLight.gameObject.SetActive(false);
}

/// <summary>
/// 加载一个模型到一张RawImage上
/// </summary>
public void LoadModelToRawImage(string path, RawImage rawImage, bool canDrag = true, Vector3 offset = default,
Quaternion rot = default, Vector3 scale = default, Action<UIRenderToTexture, GameObject> callback = null)
{
if (rawImage == null)
{
Debug.LogError("RawImage Is Null!");
return;
}

Hooks.GameObjectManager.SpawnAsync(path, (go) => {
if (go != null)
{
UIRenderToTexture renderToTexture = rawImage.GetComponent<UIRenderToTexture>();
Vector3 pos;
if (renderToTexture == null)
{
int index = m_IndexPool.Count > 0 ? m_IndexPool.Pop() : m_PoolCount++;
pos = new Vector3(10 * index, -10000, 0);

renderToTexture = rawImage.GetOrAddComponent<UIRenderToTexture>();
renderToTexture.Init(pos, true, index);
UpdateLight();
}
else
{
pos = new Vector3(10 * renderToTexture.Index, -10000, 0);
}

go.SetLayerRecursively(Constant.Layer.UIRenderToTarget);
go.transform.SetParent(m_UIModelRoot);
go.transform.localPosition = pos + offset;
go.transform.localScale = scale == default ? Vector3.one : scale;
go.transform.rotation = rot;
renderToTexture.AddTarget(go, canDrag);

callback?.Invoke(renderToTexture, go);
}
});
}

/// <summary>
/// 卸载单个
/// </summary>
public void UnLoadModelByRawImage(RawImage rawImage, GameObject go)
{
if (rawImage != null && go != null)
{
UIRenderToTexture renderToTexture = rawImage.GetComponent<UIRenderToTexture>();
int id = go.GetInstanceID();
if (renderToTexture != null && renderToTexture.Targets != null && renderToTexture.Targets.ContainsKey(id))
{
renderToTexture.Targets.Remove(id);
Hooks.GameObjectManager.Recycle(go);
if (renderToTexture.Targets.Count == 0)
{
renderToTexture.ResetTarget();
m_IndexPool.Push(renderToTexture.Index);
}
}
UpdateLight();
}
}

/// <summary>
/// 卸载所有
/// </summary>
public void UnLoadModelByRawImage(RawImage rawImage)
{
if (rawImage != null)
{
UIRenderToTexture renderToTexture = rawImage.GetComponent<UIRenderToTexture>();
if (renderToTexture != null && renderToTexture.Targets != null)
{
foreach (var target in renderToTexture.Targets.Values)
{
Hooks.GameObjectManager.Recycle(target.gameObject);
}
renderToTexture.ResetTarget();
m_IndexPool.Push(renderToTexture.Index);
}
UpdateLight();
}
}

private void UpdateLight()
{
m_UIModelLight.gameObject.SetActive(m_CameraPool.Count < m_PoolCount);
}

public Camera SpawnCamera()
{
if (m_CameraPool.Count > 0)
{
Camera camera = m_CameraPool.Pop();
camera.gameObject.SetActive(true);
return camera;
}
else
{
GameObject go = new GameObject("CameraRTT");
go.transform.SetParentEx(m_UIModelRoot);
Camera camera = go.GetOrAddComponent<Camera>();
camera.fieldOfView = 30;
camera.allowHDR = false;
camera.backgroundColor = Color.clear;
camera.useOcclusionCulling = false;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.cullingMask = 1 << Constant.Layer.UIRenderToTarget;
camera.farClipPlane = 30;
camera.orthographicSize = 1;

return camera;
}
}

public void RecyleCamera(Camera camera)
{
if (camera != null)
{
camera.targetTexture = null;
camera.gameObject.SetActive(false);
m_CameraPool.Push(camera);
}
}
}

public class UIRenderToTexture : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler, IPointerClickHandler
{
public struct Target
{
public GameObject gameObject;
public bool canDrag;
}

private Camera m_Camera;
private RawImage m_RawImage;
private RenderTexture m_RenderTexture;
private Dictionary<int, Target> m_Targets;
private int m_Index;
private Transform m_DragTarget;

public Dictionary<int, Target> Targets => m_Targets;
public int Index => m_Index;

void OnEnable()
{
InitCamera();

if (m_RawImage == null)
m_RawImage = GetComponent<RawImage>();

if (m_Targets != null)
{
foreach (var target in m_Targets.Values)
{
if(target.gameObject != null)
target.gameObject.SetActive(true);
}
}
if (m_RenderTexture == null)
{
m_RenderTexture = RenderTexture.GetTemporary((int)m_RawImage.rectTransform.rect.width, (int)m_RawImage.rectTransform.rect.height, 1);
m_RenderTexture.name = "UIRenderToTexture";
}
m_Camera.targetTexture = m_RenderTexture;
m_RawImage.texture = m_RenderTexture;
}

void OnDisable()
{
if (m_Targets != null)
{
foreach (var target in m_Targets.Values)
{
if(target.gameObject != null)
target.gameObject.SetActive(true);
}
}

if (m_Camera != null)
Hooks.UIModelManager.RecyleCamera(m_Camera);

ReleaseRenderTexture();
}

void OnDestroy()
{
if (m_Camera != null)
Hooks.UIModelManager.RecyleCamera(m_Camera);
ReleaseRenderTexture();
}

private void InitCamera(bool orth = true)
{
if (m_Camera == null)
{
m_Camera = Hooks.UIModelManager.SpawnCamera();
}
else
{
m_Camera.gameObject.SetActive(true);
}
m_Camera.orthographic = orth;
}

private void ReleaseRenderTexture()
{
if (m_RenderTexture != null)
{
RenderTexture.ReleaseTemporary(m_RenderTexture);
m_RenderTexture = null;
m_RawImage.texture = null;
}
}

public void Init(Vector3 vec, bool orth, int index)
{
InitCamera(orth);
m_Index = index;
if (m_Camera)
{
m_Camera.transform.localPosition = vec + new Vector3(0, 0, 20);
m_Camera.transform.localEulerAngles = new Vector3(0, 180, 0);
}
}

public void AddTarget(GameObject target, bool canDrag)
{
if (target)
{
if (m_Targets == null)
m_Targets = new Dictionary<int, Target>();
m_Targets.Add(target.GetInstanceID(), new Target { gameObject = target, canDrag = canDrag });
}
}

public void ResetTarget()
{
m_Targets.Clear();
m_Targets = null;
if (m_Camera != null)
Hooks.UIModelManager.RecyleCamera(m_Camera);
ReleaseRenderTexture();
}

private void Update()
{
if (m_Targets != null)
{
foreach (var target in m_Targets.Values)
{
if(m_DragTarget == null || m_DragTarget.gameObject != target.gameObject)
target.gameObject.transform.rotation = Quaternion.Lerp(target.gameObject.transform.rotation, Quaternion.identity, Time.deltaTime * 5);
}
}
}

private void SetClickTarget(Vector2 clickPos)
{
// 通过射线检测当前点击的模型
// 拿到点击位置与UI位置的偏移百分比
Camera uiCamera = Hooks.CameraManager.UICamera;
float width = m_RawImage.rectTransform.rect.width;
float height = m_RawImage.rectTransform.rect.height;
Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, transform.position);
screenPos += new Vector2(width * (0.5f - m_RawImage.rectTransform.pivot.x), height * (0.5f - m_RawImage.rectTransform.pivot.y));
Vector2 offset = clickPos - screenPos;
offset = new Vector2(offset.x / width, offset.y / height);
if (m_Camera.orthographic)
{
// 正交相机的渲染大小height=size*2, width = 宽高比*height
float screenHeight = m_Camera.orthographicSize * 2;
float screenWidth = m_Camera.pixelWidth * 1.0f / m_Camera.pixelHeight * screenHeight;
Vector3 startPoint = m_Camera.transform.position + m_Camera.transform.right * offset.x * screenWidth + m_Camera.transform.up * offset.y * screenHeight;

Debug.DrawLine(startPoint, startPoint + m_Camera.transform.forward * 100, Color.red, 5f);
if (Physics.Raycast(startPoint, m_Camera.transform.forward, out RaycastHit hit, 100, 1 << Constant.Layer.UIRenderToTarget))
{
m_DragTarget = hit.transform;
}
}
else
{
// 透视相机通过FOV和near可以求height, width同正交相机
float screenHeight = Mathf.Tan(m_Camera.fieldOfView * 0.5f * Mathf.Deg2Rad) * m_Camera.nearClipPlane * 2;
float screenWidth = m_Camera.pixelWidth * 1.0f / m_Camera.pixelHeight * screenHeight;
Vector3 endPoint = m_Camera.transform.position + m_Camera.transform.forward * m_Camera.nearClipPlane
+ m_Camera.transform.right * offset.x * screenWidth + m_Camera.transform.up * offset.y * screenHeight;
Vector3 dir = (endPoint - m_Camera.transform.position).normalized;

Debug.DrawLine(m_Camera.transform.position, endPoint, Color.red, 5f);
if (Physics.Raycast(m_Camera.transform.position, dir, out RaycastHit hit, m_Camera.farClipPlane, 1 << Constant.Layer.UIRenderToTarget))
{
m_DragTarget = hit.transform;
}
}
}

public void OnBeginDrag(PointerEventData eventData)
{
m_DragTarget = null;
SetClickTarget(eventData.position);

// 不能拖拽时置空
if (m_DragTarget != null && m_Targets.TryGetValue(m_DragTarget.GetInstanceID(), out Target target) && !target.canDrag)
{
m_DragTarget = null;
}
}

public void OnDrag(PointerEventData eventData)
{
if (m_DragTarget != null)
m_DragTarget.localEulerAngles -= new Vector3(0, eventData.delta.x, 0);
}

public void OnEndDrag(PointerEventData eventData)
{
m_DragTarget = null;
}

public void OnPointerClick(PointerEventData eventData)
{
m_DragTarget = null;
SetClickTarget(eventData.position);

if (m_DragTarget != null)
{
// 点中模型,发送事件或者其他操作
}
}
}
}

ScrollView扩展