Unity 限制摄像机在一定区域内移动

本案例中移动模型所采用的方案为:移动摄像机; 那么要限制模型的移动范围(该教程中,只允许模型在可视范围内移动,防止模型被移动丢失),可以考虑采用限制摄像机的范围。

来回忆以下知识点试试~

  • 向量的相加
  • 向量的相减
  • 向量的叉乘、点乘
  • 向量的投影
  • 参考资料 : http://www.cnblogs.com/graphics/archive/2009/10/17/1585281.html
    在同一平面的线段的交点

    看图说话~

    首先明白,空间的点也可以看做一个向量! 什么叫向量(矢量)呢?(有大小、方向的量)

    若t >= 0, 则射线与平面相交,且交点为p0 + tu,若t < 0,则不相交。(注意这里,n不可约去,因为做的是点积,而不是普通乘法)

    ** 说明: n是法向量的单位向量; u 是射线的单位向量;p1 为平面上的一点,p0为交点**

    其中的重点知识来了: 求在同一平面的两线段的交点~~

    分为两步骤:

    判断两线段是否相交

    ** 判断是否相交**,用到跨立实验-- 传送门: http://www.cnblogs.com/dwdxdy/p/3230485.html

    如果两线段相交,则两线段必然相互跨立对方。
    若P1P2跨立Q1Q2,则矢量(P1-Q1)和(P2-Q1)位于矢量(Q2-Q1)的两侧,即( P1 - Q1 ) × ( Q2 - Q1 ) * ( P2 - Q1 ) × ( Q2 - Q1 ) < 0。

    若Q1Q2跨立P1P2,则矢量(Q1-P1)和(Q2-P1)位于矢量(P2-P1)的两侧,即( Q1 - P1 ) × ( P2 - P1 ) * ( Q2 - P1 ) × ( P2 - P1 ) < 0。

    排斥实验和跨立实验的示例如下图所示

    P表示摄像机的位置,平面 abcd 表示上面我所创建的面,P0表示摄像机在面上的投影,t 表示模型中心点,Q表示 P0t 与线段 bd的交点;s表示超出距离;P`P 表示摄像机应该回退的方向。

    限定摄像机的移动范围,返回摄像机的边缘位置

    <summary> ///限定摄像机的移动范围 ///</summary> ///<param name="originDesPosition"></param> ///<param name="offset"></param> private Vector3 RetrictMove() { // 1.求摄像机在平面上的投影点 p Plane plane = CreatePlaneThatCrossModelCenterAndPerpendicularToCameraForward(); Vector3 projectDot = ProjectDotOfCameraToPlane(plane); // p Vector3[] corner = caculateCameraBounds(projectDot,Math.Abs(plane.GetDistanceToPoint(transform.position)));// 计算摄像机的可移动范围 // 2.求投影点与模型中心所组成的线段与各个视口边缘的交点 Vector3 crossDot = Vector3.zero; Vector3 modelCenter = Vector3.zero;// 模型中心 m if (AvatarLoader.Instance.thisSceneStat.modelStatusMgr.IsSelected()) modelCenter = AvatarLoader.Instance.thisSceneStat.modelStatusMgr.GetSelectedCenter(); // 获取被选中模型的中心 (由于篇幅问题,此教程未讲解此算法) modelCenter = AvatarShowCtrl.Instance.GetCurrentModleCenter(); // 获取所有所有模型的平均中心(由于篇幅问题,此教程未讲解此算法) crossDot = IntersectionDotOfTwoSegmentInSamePlane(projectDot, modelCenter, corner[0], corner[1]); // 顶 线段与 pm 线段的交点 if (crossDot == Vector3.zero) { // 表示线段与ab线段未相交 crossDot = IntersectionDotOfTwoSegmentInSamePlane(projectDot, modelCenter, corner[1], corner[3]); // 右 线段与 pm 线段的交点 if (crossDot == Vector3.zero)// 表示线段与bc线段未相交 crossDot = IntersectionDotOfTwoSegmentInSamePlane(projectDot, modelCenter, corner[2], corner[3]);// 底 线段与 pm 线段的交点 if (crossDot == Vector3.zero)// 表示线段与dc线段未相交 crossDot = IntersectionDotOfTwoSegmentInSamePlane(projectDot, modelCenter, corner[0], corner[2]);// 左 线段与 pm 线段的交点 if (crossDot != Vector3.zero && crossDot != projectDot) {// 表示得到pm线段与视口边缘的交点 //Debug.DrawLine(transform.position, crossDot, Color.red); // 让摄像机反向移动 获取反方移动距离长度(交点与模型中心的距离) float distance = Vector3.Distance(crossDot, modelCenter); if (distance != 0) Vector3 cameralMoveDirection = (modelCenter - crossDot).normalized; // 摄像机移动的方向 Vector3 cameraDest = transform.position + distance * cameralMoveDirection; // 获取摄像机移动后的位置 射线公式 p = p0+tu p0为起点t可看做长度u可看做单位向量 return cameraDest; return Vector3.zero;

    创建一垂直于摄像机正方向向量、并且过模型中心点的面

    /// <summary>
        /// 创建一垂直于摄像机正方向向量、并且过模型中心点的面
        /// </summary>
        /// <returns></returns>
        private Plane CreatePlaneThatCrossModelCenterAndPerpendicularToCameraForward() {
            Vector3 normal = CameraCtrl.Instance.transform.forward; // 摄像机的正反向
            normal = normal.normalized;
            Vector3 center = AvatarShowCtrl.Instance.GetCurrentModleCenter();
            Plane plane = new Plane(-normal, center); // 以摄像机的正方向为法向量、并且过模型中心点做一平面
            return plane;
    

    求摄像机在对应平面的投影点

    /// <summary>
        /// 求摄像机在对应平面的投影点
        /// </summary>
        /// <param name="plane"></param>
        /// <returns></returns>
        private Vector3 ProjectDotOfCameraToPlane(Plane plane) {
            float distance = plane.GetDistanceToPoint(transform.position);
            Vector3 projectDot = Math.Abs(distance) * transform.forward + transform.position;
            return projectDot;
    

    计算摄像机的移动范围

    /// <summary>
        /// 计算摄像机的移动范围
        /// </summary>
        /// <param name="projectDotOfCamera">摄像机在平面(过模型中心与摄像机正方向垂直的平面)的投影点</param>
        /// <returns></returns>
        private Vector3[] caculateCameraBounds(Vector3 projectDotOfCamera,float distance) {
            Vector3[] corners = new Vector3[4];
            float aspect = Camera.main.aspect; // 长宽比
            float WindowHeight = WindowFactorK * distance + WindowFactorB; // 线性公式
            float height = WindowHeight;
            float width = height * aspect;
            // 利用摄像方程
            // Top Left dot
            corners[0] = (projectDotOfCamera + width * transform.right) + height * transform.up;
            // Top Right dot
            corners[1] = (projectDotOfCamera - width * transform.right) + height * transform.up;
            // Bottom Left dot
            corners[2] = (projectDotOfCamera + width * transform.right) - height * transform.up;
            // Bottom Right dot
            corners[3] = (projectDotOfCamera - width * transform.right) - height * transform.up;
            return corners;
    

    计算在同一平面相交两条线段的交点

    /// <summary>
        /// 计算在同一平面相交两条线段的交点
        /// </summary>
        /// <param name="a">线段A的起点</param>
        /// <param name="b">线段A的终点</param>
        /// <param name="c">线段B的起点</param>
        /// <param name="d">线段B的终点</param>
        /// <returns></returns>
        private Vector3 IntersectionDotOfTwoSegmentInSamePlane(Vector3 a, Vector3 b,Vector3 c, Vector3 d) {
            // 先判断两射线是否相交
            if(!IsCrossedToTwoSegmentInSamePlane(a,b,c,d)){
                return Vector3.zero;
            // 分别求点c、点d在向量ab(A)上的投影点,由此可以分别求得点c、点d距离线段ab的距离s1、s2
            Vector3 projectDotC = ProjectDotOfVector(a,c,b); // c 在ab上的投影点
            Vector3 projectDotD = ProjectDotOfVector(a,d,b);// d 在ab上的投影点
            float s1 = Vector3.Distance(projectDotC,c); // 点c 在线段的距离 s1
            float s2 = Vector3.Distance(projectDotD,d); // 点d 在线段的距离 s2
            // 设交点为p(x0,y0,z0)|cp|/|pd| = s1/s2
            Vector3 p = s1 / (s1 + s2) * projectDotD + s2 / (s1 + s2) * projectDotC;
            return p;