使用背景:
游戏里有一种展示,就是将3D物体渲染到2D的平面上,玩家拖动平面旋转物体进行全方位的展示,类似于王者荣耀的英雄展示。如图:
资源包网盘地址:
链接: https://pan.baidu.com/s/1cy_TGrYhfsUOP3wTBEOhFA?pwd=5f9a 提取码: 5f9a
如何使用:
下载网盘资源,引入资源包,文件下的ModelDisplayPanelScene场景即为使用案例。
也可以创建新的场景,将ModelDisplayPanel预制体拖入场景中运行即可(注意该场景需要有EventSystem)。
如何制作该功能:
- 创建新的场景
- 创建一个Image画布调整到合适的大小(下面称它为Mask),并在该Image上添加Mask组件(该Imge用于添加底部背景,Mask用于遮罩使现实的大小达到理想的效果)
- 在Mask下创建一个RawImage(设置为1920*1080)
- 在Assest下创建一个Render Texture,设置size为1920*1080。并将该Render Texture拖给上面创建的RawImage—>Texture。
- 将需要的3D模型放入场景
- 新建一个渲染层命名为Model(Inspector面板右上角Layer—>Add Layer—>Layers—>找一个没被占用的填上“Model”),然后将上面需要现实的3D物体设置为Model层(Layers—>Model),如下:
- 创建一个新的相机(命名为ModelCamera),设置相机Clear Flags为Solid Color,设置Culling Mask为仅Model,将上面创建的Render Texture赋值给Target Texture,如下图,调整该相机和物体的距离,使该相机能渲染到物体。
- 挂载ShowModelRawImage脚本到上面创建的RawImage上,该脚本依赖其他两个脚本,分别为Singleton和ToolBox。三个脚本如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; /// <summary> /// 在图片上显示模型并旋转(挂载到指定鼠标移动的image) /// (直接调用该脚本的Init函数) /// </summary> public class ShowModelRawImage : MonoBehaviour { private Camera modelCamera;//渲染模型的相机 private GameObject nowModel; private Transform originalPos; private RectTransform modelPicRect; // 模型图片rect(用于判定鼠标是否在图片上) private bool isResetting = false; // 模型回退到原来的状态 private float originalMouseX = 0.0f; private float originalMouseY = 0.0f; private float mouseXCount = 0.0f; private float mouseYCount = 0.0f; private float lerpX = 0.0f; private float lerpY = 0.0f; private bool isXFinished = false; private bool isYFinished = false; private bool isScaleFinished = false; // 用于判定是否缩放完成 [Header("旋转角度恢复原状速度")] public float resetSpeed = 5.0f; // 模型恢复原装速度 [Header("摄像机到模型的距离")] public float defaultDistanc = 5.0f; [Header("缩放速度")] public float zoomSpeed = 1.0f; [Header("缩放比例恢复速度")] public float resetZoomSpeed = 10.0f; [Header("模型放大的最大倍数(摄像机最近的距离)")] public float minDistanceRatio = 1.0f; // 摄像机到模型的最小距离等级 [Header("模型缩小的最大倍数(摄像机最远距离)")] public float maxDistanceRatio = 10.0f; // 摄像机到模型的最大距离等级 [Header("摄像机初始设置距离,代表的倍数(若摄像机距离为10,则缩放到3倍就是距离到10的位置)")] public float initDistanceRatio = 3.0f; // 计算规则相当复杂 private float originalDistance = 0.0f; // 最开始摄像机到模型中心的距离 private float perDistence = 0.0f; // 每一级缩放的距离 // originalDistance保存一开始摄像机到模型中心的位置 // 根据initDistanceRatio为初始设定的距离的放大比例,当前为3倍,也就是初始设定为3倍大小 // perDistence则保存每一倍大小的距离,使用初始距离除以初始放大倍数可以得到这个值 // minDistanceRatio和maxDistanceRatio则为最小的放大倍数和最大的放大倍数 private void Awake() { modelPicRect = GetComponent<RectTransform>(); } /// <summary> /// 展示用的摄像机和需要展示的模型(直接调用该函数) /// </summary> /// <param name="modelCamera">模型渲染相机</param> /// <param name="showMedel">模型</param> public void Init(Camera newCamera, GameObject showModel) { modelCamera = newCamera; nowModel = showModel; originalPos = modelCamera.transform; originalDistance = Vector3.Distance(modelCamera.transform.position, showModel.transform.position); perDistence = originalDistance / initDistanceRatio; } // Update is called once per frame void Update() { if (isResetting) { if (!isXFinished) { float rotateSpeedX = lerpX * Time.deltaTime * resetSpeed; modelCamera.transform.RotateAround(nowModel.transform.position, Vector3.up, rotateSpeedX); mouseXCount += -rotateSpeedX; if (Mathf.Abs(mouseXCount - originalMouseX) <= Mathf.Abs(rotateSpeedX)) { isXFinished = true; } } if (!isYFinished) { float rotateSpeedY = lerpY * Time.deltaTime * resetSpeed; modelCamera.transform.RotateAround(nowModel.transform.position, modelCamera.transform.right, rotateSpeedY); mouseYCount += -rotateSpeedY; if (Mathf.Abs(mouseYCount - originalMouseY) <= Mathf.Abs(rotateSpeedY)) { isYFinished = true; } } if (isYFinished && isXFinished) { originalMouseX = 0.0f; originalMouseY = 0.0f; mouseXCount = 0.0f; mouseYCount = 0.0f; } if (!isScaleFinished) { float nowDis = Vector3.Distance(modelCamera.transform.position, nowModel.transform.position); float rScaleDir = (nowDis - defaultDistanc >= 0) ? 1 : -1; if (nowDis - defaultDistanc <= 0.001f) { isScaleFinished = true; } modelCamera.transform.Translate(Vector3.forward * Time.deltaTime * resetZoomSpeed * rScaleDir); } if (isYFinished && isXFinished && isScaleFinished) { modelCamera.transform.position = originalPos.position; modelCamera.transform.rotation = originalPos.rotation; isXFinished = false; isYFinished = false; isScaleFinished = false; isResetting = false; } return; } if (ToolBox.Instance.IsOverUI(modelPicRect)) { if (nowModel == null) return; if (Input.GetMouseButton(1)) { float mouse_x = Input.GetAxis("Mouse X"); //获取鼠标X轴移动 float mouse_y = -Input.GetAxis("Mouse Y"); //获取鼠标Y轴移动 originalMouseX += mouse_x * 5; originalMouseY += mouse_y * 5; modelCamera.transform.RotateAround(nowModel.transform.position, Vector3.up, mouse_x * 5); modelCamera.transform.RotateAround(nowModel.transform.position, modelCamera.transform.right, mouse_y * 5); } if (Input.GetAxis("Mouse ScrollWheel") != 0) { float b = Vector3.Distance(modelCamera.transform.position, nowModel.transform.position); float c = Input.GetAxis("Mouse ScrollWheel"); //滑轮滑动 if (c > 0 && b > originalDistance - (initDistanceRatio - minDistanceRatio) * perDistence) modelCamera.transform.Translate(Vector3.forward * zoomSpeed); if (c < 0 && b < originalDistance + (maxDistanceRatio - initDistanceRatio) * perDistence) modelCamera.transform.Translate(Vector3.forward * -zoomSpeed); } } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class ToolBox : Singleton<ToolBox> { //private ToolMono toolMono; //public ToolMono ToolMono //{ // get { // if (!toolMono) // return null; // return toolMono; // } // set { toolMono = value; } //} /// <summary> /// 判定鼠标是否悬停在指定的ui上面(不判定ui是否被挡住) /// </summary> /// <param name="rect">指定ui的rect组件</param> /// <returns></returns> public bool IsOverUI(RectTransform rect) { if (!rect) return false; if (rect.gameObject.activeInHierarchy && RectTransformUtility.RectangleContainsScreenPoint(rect, Input.mousePosition)) return true; return false; } private PointerEventData eventData = new PointerEventData(EventSystem.current); private List<RaycastResult> raycastResults = new List<RaycastResult>(); /// <summary> /// 判定鼠标是否停在指定ui上面(只会判定能被点击到的ui) /// </summary> /// <param name="go">指定uiGameObject</param> /// <returns></returns> public bool IsOverUI(GameObject go) { if (!go) return false; eventData.position = Input.mousePosition; raycastResults.Clear(); //向点击位置发射一条射线,检测是否点击UI EventSystem.current.RaycastAll(eventData, raycastResults); if (raycastResults.Count > 0) { foreach (RaycastResult result in raycastResults) { if (go.Equals(result.gameObject)) return true; } return false; } else { return false; } } /// <summary> /// 判定鼠标是否停在指定ui(ui在最上层)上面 /// </summary> /// <param name="go">指定uiGameObject</param> /// <returns></returns> public bool IsOverUIOnTop(GameObject go) { if (!go) return false; eventData.position = Input.mousePosition; raycastResults.Clear(); //向点击位置发射一条射线,检测是否点击UI EventSystem.current.RaycastAll(eventData, raycastResults); if (raycastResults.Count > 0) { if (go.Equals(raycastResults[0].gameObject)) return true; return false; } else { return false; } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Singleton<T> where T : new() { private static T instance; public static T Instance { get { if (instance == null) instance = new T(); return instance; } } }
- 创建测试运行脚本MainController如下,将脚本挂载在场景上,拖入对应需要的物件或物体:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class MainController : MonoBehaviour { [Header("ShowModelRawImage脚本")] public ShowModelRawImage showModelRawImage; [Header("渲染模型的相机")] public Camera modelCamera; [Header("需要渲染的模型")] public GameObject model; // Start is called before the first frame update void Start() { //直接调用ShowModelRawImage脚本的Init函数,将模型渲染相机和需要渲染的模型传入参数 showModelRawImage.Init(modelCamera, model); } // Update is called once per frame void Update() { } }
- 最后还要修改下主相机的渲染,取消主相机Model层的渲染。
最后查看场景里是否有EventSystem(如没有需要创建Hierarchy面板右键—>UI—>EventSystem),运行即可,详细的可查看网盘链接实例场景。