方法有很多,我只介绍一种我认为最简单有效的方法,用非常取巧的手段,使用RenderTexture + Camera 在ScrollView里展示3D模型。
效果:
1.首先创建一个RenderTexture用于渲染3D元素,然后在ScrollView里创建一个RawImage,用于把RenderTexture上的内容显示在UI上:
2.代码动态创建一个Camera,用于把3D模型渲染到RenderTexture上:
[SerializeField] RenderTexture renderModelTex = null;//就是上一步创建的RenderTexture
Camera renderCam = null;
Vector2 screenWroldSize;
Vector3 renderCamOffset = Vector3.zero;
Vector3 contentRawPos;
void Awake(){
renderCam = new GameObject("RenderCamera").AddComponent<Camera>();
renderCam.orthographic = true;
renderCam.transform.position = new Vector3(0, 0, -10);
renderCam.transform.rotation = Quaternion.identity;
renderCam.clearFlags = CameraClearFlags.SolidColor;
renderCam.cullingMask = LayerMask.GetMask("TransparentFX");
renderCam.targetTexture = renderModelTex;
renderImage.GetComponent<RawImage>().texture = renderModelTex;
screenWroldSize = GF.Instance.uiCam.ViewportToWorldPoint(new Vector3(1, 1, GetComponent<Canvas>().planeDistance));
}
3.创建ScrollView列表元素:
这里为了取巧,使用协程创建列表元素,因为要等Unity 的Layout组件在下一帧自动布局排列完成,然后在排列后的Item位置创建3D模型。
IEnumerator CreateListView(int index)
{
//清空滚动列表里的UI item
GF.UI.RemoveAllChildren(listView.content);
//清空UI item对应的3D模型
foreach (var item in skinModelList)
{
GF.Entity.HideEntitySafe(item);
}
skinModelList.Clear();
curSelectSkinItem = null;
List<int> ownSkinList = null;
int curWearSkin = 0;
int itemOrder = 0;
var skinTb = GF.DataTable.GetDataTable<SkinTable>();
SkinTable[] skinDtRows = skinTb.GetAllDataRows();
//创建N个UI Item
for (int i = 0; i < skinRowList.Count; i++)
{
Instantiate(itemPfb, listView.content);
}
//需要等下一帧Layout组件自动布局排列完成后再创建模型
yield return new WaitForEndOfFrame();
float rectSize = Mathf.Max(listView.viewport.rect.size.x, listView.viewport.rect.size.y);
//动态的将渲染图改变为滚动列表的大小
renderImage.sizeDelta = new Vector2(rectSize, rectSize);
//同时更新相机视口的大小 保持一致
var wSize = screenWroldSize.y * (rectSize / this.rectTransform().rect.height);
renderCam.orthographicSize = wSize;
var camPos = renderImage.position;
camPos.z = renderCam.transform.position.z;
renderCam.transform.position = camPos;
renderCamOffset = camPos;
//记录滚动列表Content的初始位置, 以便滑动列表时根据Content的位置偏移更新Camera的位置
contentRawPos = listView.content.position;
foreach (Transform item in listView.content)
{
var skinItem = item.GetComponent<SkinItem>();
var pos = skinItem.transform.position;
pos.z = 1;
//在排列好的列表元素位置创建3D模型
SkinPreviewEntityData eDt = ReferencePool.Acquire<SkinPreviewEntityData>().FillArgs(index, 1);
eDt.Position = pos + itemPosOffset;
eDt.Rotation = itemRotation;
eDt.Scale = itemScale;
var skinModelId = GF.Entity.ShowEntity(typeof(SkinPreviewEntity), skinItem.SkinData.PrefabName, Const.EntityGroup.ScrollViewModel, eDt);
skinModelList.Add(skinModelId);
}
}
4.使Camera跟随ScrollView的滑动,从而达到3D模型跟随列表滚动的效果:
public void OnScrollValue(Vector2 pos)
{
Vector3 camPos = renderCamOffset - (listView.content.position - contentRawPos);
renderCam.transform.position = camPos;
}
把此函数拖到ScrollView的滚动事件上就大功告成了。
总解:通过此方式可以完美的把3D模型以UI元素的形式渲染到UI上层,不用考虑3D模型的遮罩问题。让Camera跟随ScrollView的Content节点移动,与Layout组件互不影响,随便怎么布局,最终模型展示的位置跟随UI Item创建的位置。