unity笔记——游戏里常见的模型展示(3D物体渲染到2D平面,旋转查看)

使用背景:

游戏里有一种展示,就是将3D物体渲染到2D的平面上,玩家拖动平面旋转物体进行全方位的展示,类似于王者荣耀的英雄展示。如图:

资源包网盘地址:

链接: https://pan.baidu.com/s/1cy_TGrYhfsUOP3wTBEOhFA?pwd=5f9a 提取码: 5f9a

如何使用:

下载网盘资源,引入资源包,文件下的ModelDisplayPanelScene场景即为使用案例。

也可以创建新的场景,将ModelDisplayPanel预制体拖入场景中运行即可(注意该场景需要有EventSystem)。

如何制作该功能:

  1. 创建新的场景
  2. 创建一个Image画布调整到合适的大小(下面称它为Mask),并在该Image上添加Mask组件(该Imge用于添加底部背景,Mask用于遮罩使现实的大小达到理想的效果)
  3. 在Mask下创建一个RawImage(设置为1920*1080)
  4. 在Assest下创建一个Render Texture,设置size为1920*1080。并将该Render Texture拖给上面创建的RawImage—>Texture。

  5. 将需要的3D模型放入场景
  6. 新建一个渲染层命名为Model(Inspector面板右上角Layer—>Add Layer—>Layers—>找一个没被占用的填上“Model”),然后将上面需要现实的3D物体设置为Model层(Layers—>Model),如下:

  7. 创建一个新的相机(命名为ModelCamera),设置相机Clear Flags为Solid Color,设置Culling Mask为Model,将上面创建的Render Texture赋值给Target Texture,如下图,调整该相机和物体的距离,使该相机能渲染到物体。
  8. 挂载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;
            }
        }
    }
  9. 创建测试运行脚本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()
        {
            
        }
    }
    

  10. 最后还要修改下主相机的渲染,取消主相机Model层的渲染。

最后查看场景里是否有EventSystem(如没有需要创建Hierarchy面板右键—>UI—>EventSystem),运行即可,详细的可查看网盘链接实例场景。