- 使用拖动地图,镜头固定的方式,能对touch点进行精确的拖动,不受透视影响.
- 边界控制(可视范围控制)是通过判断FOV的4个点(也是屏幕的4个角)与地图的hit交点是否在指定的范围内来做的.
- 缩放是通过移动镜头高度来做的.
- 提供定点缩放, 缩放时如果超出可视范围,会对缩放点进行移动.
- 需要在Hierarchy中添加EasyTouch.
- 如果要想使用镜头移动方式,具体参考: Unity: 一个简单的镜头移动/缩放管理类(只移动镜头方式)
代码如下:
using UnityEngine;
using HedgehogTeam.EasyTouch;
using XLua;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
/// <summary>
/// 摄像机管理类: 固定摄像机,移动地图的方式.
/// 挂载到摄像机所在的GameObject
/// </summary>
[RequireComponent(typeof(Camera))]
[LuaCallCSharp]
public class CameraViewFixedCam : MonoBehaviour
{
[Header("moveTarget可视边界(localPos):左上->右上->右下->左下")]
/// 为了方便配置,设计为相对于moveTarget的localPosition,而不是全局坐标
public Vector3[] moveTargetLocalBounds = new Vector3[4]{
new Vector3(100f, 0f, 900f),
new Vector3(900f, 0f, 900f),
new Vector3(900f, 0f, 100f),
new Vector3(100f, 0f, 100f)
};
[Header("缩放时的最高高度")]
public float scaleMaxY = 160;
[Header("缩放时的最低高度")]
public float scaleMinY = 100;
[Header("缩放速度,值越大缩放越快")]
public float scaleSpeed = 35f;
[Header("moveTarget移动的目标点(缓动方式)")]
public Vector3 lerpMoveTarget = Vector3.zero;
[Header("手势滑动结束后,需要继续移动的系数,值越大移动得越远")]
public float lerpGoOnMoveScale = 1.5f;
[Header("手势滑动结束后,继续(减速)移动的速度,值越大移动得越快")]
public float lerpMoveSpeed = 10f;
public float camDistance
{
get
{
var _camDistance = 0f;
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit[] hits = Physics.RaycastAll(ray, this.rayMaxDistance);
for (int i = 0; i < hits.Length; i++)
{
if (hits[i].collider.gameObject == this.moveTarget)
{
_camDistance = hits[i].distance;
break;
}
}
this._scopeDitry = true;
this.GetScreenCornersPosInWorld();
return _camDistance;
// return this._camDistance;
}
}
[HideInInspector]
public int moveToBorderStatus = 0;
[Header("摄像机最大射线距离")]
public int rayMaxDistance = 5000;
[Header("需要移动的目标对象(必须设置)")]
public GameObject moveTarget;
private Vector4 currentScope = Vector4.zero;
[SerializeField]
[Header("摄像机中心射线和场景的距离")]
// private float _camDistance = float.NaN;
private Vector3[] _camCornersCache = new Vector3[4];
private bool _scopeDitry = true;
private bool swipeTargetValid = false;
/// swipe时记录上一次的世界坐标
private Vector3 preSwipeWorldPos;
/// 滑动期间的最后几次变化
private Vector3[] swipeIncrement = new Vector3[5];
private int swipeIncrementIndex = 0;
internal Camera mainCamera;
/// 是否要进行lerpMove
internal bool lerpMove = false;
[SerializeField]
internal bool showDebugLines = false;
// 是否正在缩放
private bool isPinching = false;
// 是否锁定并阻止swipe
private bool lockTouch = false;
private Vector3[] ScreenCorners;
private Vector3[] screenViewTrapezoid = new Vector3[4];
private void Awake()
{
ScreenCorners = new Vector3[]{
new Vector2(0,Screen.height),
new Vector2(Screen.width,Screen.height),
new Vector2(Screen.width,0),
new Vector2(0,0),
};
this.mainCamera = this.gameObject.GetComponent<Camera>();
this.lerpMove = false;
if (this.moveTarget == null)
{
throw new UnityException("必须指定一个移动目标");
}
lerpMoveTarget = this.moveTarget.transform.position;
this.GetScreenCornersPosInWorld();
EasyTouch.On_Pinch += EasyTouch_On_Pinch;
EasyTouch.On_SwipeStart += EasyTouch_On_SwipeStart;
EasyTouch.On_Swipe += EasyTouch_On_Swipe;
EasyTouch.On_SwipeEnd += EasyTouch_On_SwipeEnd;
EasyTouch.On_TouchDown2Fingers += EasyTouch_On_TouchDown2Fingers;
EasyTouch.On_TouchUp2Fingers += EasyTouch_On_TouchUp2Fingers;
}
public void LookAt(GameObject tag, bool useLerp = true)
{
this.LookAt(tag.transform.position, useLerp);
}
///<summary>移动到目标位置,并使其与摄像机中心位置对齐</summary>
public void LookAt(Vector3 worldPos, bool useLerp = true)
{
this.CancelLerpMove();
// 1.使用射线检测moveTarget的BoxCollider来得到hitPoint(世界坐标),
// 2.使用该hitPoint与worldPos的向量差得到偏移量,
// 所以需要设置boxCollider的size.y,使其和worldPos.y一致,
// 这样才使得worldPos的y平面位于屏幕中心
var bc = this.moveTarget.GetComponent<BoxCollider>();
var size = bc.size;
size.y = (worldPos.y - this.moveTarget.transform.position.y) * 2;
bc.size = size;
// 获取相机中心射线检测到的位置
Ray ray = new Ray(this.transform.position, transform.forward);
var hits = Physics.RaycastAll(ray, this.rayMaxDistance);
var camRayPoint = worldPos;
if (hits != null)
{
for (int i = 0; i < hits.Length; i++)
{
if (hits[i].collider.gameObject == this.moveTarget)
camRayPoint = hits[i].point;
}
}
var transOff = camRayPoint - worldPos;
transOff.y = 0;
transOff = this.WrapPosInRect(transOff, true);
this.MoveTo(this.moveTarget.transform.position+transOff, useLerp);
}
/// <summary>移动到相对于moveTarget的位置</summary>
public void LookAtLocal(Vector3 tagLocalPos, bool useLerp = true)
{
var tagPos = this.moveTarget.transform.TransformPoint(tagLocalPos);
this.LookAt(tagPos, useLerp);
}
public void MoveTo(Vector3 tagPos, bool useLerp = true)
{
// this._scopeDitry = true;
this.lerpMove = useLerp;
if (useLerp)
{
this.lerpMoveTarget = tagPos;
}
else
{
this.moveTarget.transform.position = tagPos;
}
}
public void Scale(float scaleDelta, Vector2 scaleCenterPosOnScreen)
{
// 计算摄像机视口(摄像机显示画面)的宽高
float halfFOV = (this.mainCamera.fieldOfView * 0.5f) * Mathf.Deg2Rad;
float aspect = this.mainCamera.aspect;
// 视口在Z轴上变化时(相当于缩放效果),对应的宽高变化量,相当于直接使用scaleDelta作为Z轴的变化距离
// 乘2是因为scaleDelta * Mathf.Tan(halfFOV)计算出的只是视口画面的一半高,缩放是全画面,需要乘2
float height = scaleDelta * Mathf.Tan(halfFOV) * 2;
float width = height * aspect;
// 缩放中心点在屏幕中的比例,减0.5f,因为世界坐标是相对于屏幕的中心
float cpRateX = scaleCenterPosOnScreen.x / Screen.width - 0.5f;
float cpRateY = scaleCenterPosOnScreen.y / Screen.height - 0.5f;
Vector3 pos = this.transform.position;
var oldPos = this.transform.position;
// scaleW*cpRateX 表示视口画面宽度变化偏移度.
// 如果cpRateX,cpRateY都为0,表示X轴,Y轴上无变化,则只以transform.forward为实际变化,效果为沿着视口中心的路径上(Z轴)前进/后退.
// 比如cpRateX为0.2f,表示在屏幕中心右侧20%位置处作为手势缩放中心点进行操作,
// scaleW此时假如为-5(表示放大),则transform.right就还需要往左走-1f,
// 最终效果为transform.forward按scaleDelta前进,同时X轴往左移动,这样视觉上20%位置处没有发生任何偏移. Y轴同理
var w = width * cpRateX;
var h = height * cpRateY;
pos += transform.right * w;
pos += transform.up * h;
pos += transform.forward * scaleDelta;
if (pos.y <= scaleMaxY && pos.y >= scaleMinY)
{
// 进行一次预缩放
this.transform.position = pos;
this._scopeDitry = true;
this.GetScreenCornersPosInWorld();
// 缩放后对边界的检查,如果触碰到边界则需要对moveTarget进行transOff偏移,
var screenViewTrapezoid = this.GetScreenCornersPosInWorld();
var moveTargetPos = this.moveTarget.transform.position;
var st = this.CheckScreenCornerIsOutBounds(moveTargetPos);
var transOff = Vector3.zero;
transOff = this.WrapTransOff(moveTargetPos, st, transOff);
this.moveTarget.transform.Translate(transOff, Space.World);
}
}
/// <summary>获取可视区域(屏幕四个角)对应的世界坐标.
/// 可用于在小地图上显示视口的梯形范围</summary>
public Vector3[] GetScreenCornersPosInWorld()
{
if (this._scopeDitry == false) return this.screenViewTrapezoid;
this._scopeDitry = false;
for (int i = 0; i < ScreenCorners.Length; i++)
{
Ray ray = this.mainCamera.ScreenPointToRay(ScreenCorners[i]);
var hits = Physics.RaycastAll(ray, this.rayMaxDistance);
Vector3 dist0 = Vector2.zero;
dist0.z = float.MaxValue;
if (hits != null && hits.Length > 0)
{
for (int j = 0; j < hits.Length; j++)
{
if (hits[j].collider.gameObject == this.moveTarget)
{
this.screenViewTrapezoid[i] = hits[j].point;
break;
}
}
}
else
{
// 没有检测到
if (i == 0)
{
this.screenViewTrapezoid[i].x -= 1;
this.screenViewTrapezoid[i].z += 1;
}
else if (i == 1)
{
this.screenViewTrapezoid[i].x += 1;
this.screenViewTrapezoid[i].z += 1;
}
else if (i == 2)
{
this.screenViewTrapezoid[i].x += 1;
this.screenViewTrapezoid[i].z -= 1;
}
else if (i == 3)
{
this.screenViewTrapezoid[i].x -= 1;
this.screenViewTrapezoid[i].z -= 1;
}
}
}
return screenViewTrapezoid;
}
/// 取消移动
public void CancelLerpMove()
{
this.lerpMove = false;
this.lerpMoveTarget = Vector3.zero;
}
// -----------------------------------------------------
/// <summary>移动moveTarget</summary>
private void MoveWithScreenPos(Vector2 screenPosition)
{
this.CancelLerpMove();
Vector3 transOff = new Vector3(0, 0, 0);
var _ray = this.mainCamera.ScreenPointToRay(screenPosition);
var _hits = Physics.RaycastAll(_ray, this.rayMaxDistance);
for (int i = 0; i < (_hits == null ? 0 : _hits.Length); i++)
{
if (_hits[i].collider.gameObject == this.moveTarget)
{
transOff = _hits[i].point - this.preSwipeWorldPos; // 当前touch的世界坐标和上一次记录的世界坐标的方向向量
this.preSwipeWorldPos = _hits[i].point;
}
}
transOff.y = 0;
if (transOff == Vector3.zero)
{
return;
}
transOff = this.WrapPosInRect(transOff);
this.moveTarget.transform.Translate(transOff, Space.World);
// this._scopeDitry = true;
if (swipeIncrementIndex >= swipeIncrement.Length)
{
swipeIncrementIndex = 0;
}
swipeIncrement[swipeIncrementIndex] = transOff;
swipeIncrementIndex++;
}
private void OnDestroy()
{
EasyTouch.On_Pinch -= EasyTouch_On_Pinch;
EasyTouch.On_SwipeStart -= EasyTouch_On_SwipeStart;
EasyTouch.On_Swipe -= EasyTouch_On_Swipe;
EasyTouch.On_SwipeEnd -= EasyTouch_On_SwipeEnd;
EasyTouch.On_TouchUp2Fingers -= EasyTouch_On_TouchUp2Fingers;
EasyTouch.On_TouchUp2Fingers -= EasyTouch_On_TouchDown2Fingers;
}
/// <summary>
/// 缩放
/// </summary>
private void EasyTouch_On_Pinch(Gesture gesture)
{
if (this.lockTouch) return;
this.isPinching = true;
// 往外扩(放大)是负数,往内聚(缩小)是整数
float scaleDelta = gesture.deltaPinch * UnityEngine.Time.deltaTime * this.scaleSpeed;
// 缩放中心点(相对于屏幕左下角)
Vector2 scaleCenterPosOnScreen = gesture.position;
this.Scale(scaleDelta, scaleCenterPosOnScreen);
}
private void EasyTouch_On_TouchDown2Fingers(Gesture gesture)
{
this.isPinching = true;
}
private void EasyTouch_On_TouchUp2Fingers(Gesture gesture)
{
this.isPinching = false;
}
/// <summary>
/// 开始划
/// </summary>
private void EasyTouch_On_SwipeStart(Gesture gesture)
{
var ray = this.mainCamera.ScreenPointToRay(gesture.position);
var hits = Physics.RaycastAll(ray, this.rayMaxDistance);
// 没有点击到目标
if (hits == null || hits.Length < 1)
{
swipeTargetValid = false;
return;
}
for (int i = 0; i < hits.Length; i++)
{
if (hits[i].collider.gameObject == this.moveTarget)
{
this.preSwipeWorldPos = hits[i].point;
break;
}
}
swipeTargetValid = true;
this.CancelLerpMove();
}
/// <summary>
/// 划
/// </summary>
private void EasyTouch_On_Swipe(Gesture gesture)
{
if (this.lockTouch || this.isPinching || !this.swipeTargetValid) return;
this.MoveWithScreenPos(gesture.position);
}
/// <summary>
/// 开始划
/// </summary>
private void EasyTouch_On_SwipeEnd(Gesture gesture)
{
if (swipeTargetValid == false) return;
this.lerpMove = true;
// 根据最后几帧的移动变化量来确定最后需要的持续移动强度
var transOff = new Vector3(0, 0, 0);
swipeIncrementIndex = 0;
for (int i = 0; i < swipeIncrement.Length; i++)
{
transOff += swipeIncrement[i];
swipeIncrement[i] = Vector3.zero;
}
transOff = this.WrapPosInRect(transOff);
this.MoveTo(this.moveTarget.transform.position + transOff, true);
}
private void LateUpdate()
{
if (this.lerpMove && this.lerpMoveTarget != Vector3.zero)
{
var dist = Vector3.Distance(this.moveTarget.transform.position, this.lerpMoveTarget);
if (dist >= 0.001f)
{
var curPos = Vector3.Lerp(this.moveTarget.transform.position, this.lerpMoveTarget, Time.deltaTime * this.lerpMoveSpeed);
this.moveTarget.transform.position = curPos;
}
else
{
this.CancelLerpMove();
}
}
#if UNITY_EDITOR
if (!showDebugLines) return;
// 可视范围边界
var moveTargetPos = this.moveTarget.transform.position;
var p0 = moveTargetPos + moveTargetLocalBounds[0];
var p1 = moveTargetPos + moveTargetLocalBounds[1];
var p2 = moveTargetPos + moveTargetLocalBounds[2];
var p3 = moveTargetPos + moveTargetLocalBounds[3];
Debug.DrawLine(p0, p1, Color.blue); // UpperLeft -> UpperRight
Debug.DrawLine(p1, p2, Color.blue); // UpperRight -> LowerRight
Debug.DrawLine(p2, p3, Color.blue); // LowerRight -> LowerLeft
Debug.DrawLine(p3, p0, Color.blue); // LowerLeft -> UpperLeft
// cam中心线
Debug.DrawLine(transform.position, transform.position + transform.forward * this.rayMaxDistance, Color.green);
// cam视锥截面
Vector3[] corners = this.GetCorners(this.camDistance);
Debug.DrawLine(corners[0], corners[1], Color.red); // UpperLeft -> UpperRight
Debug.DrawLine(corners[1], corners[2], Color.red); // UpperRight -> LowerRight
Debug.DrawLine(corners[2], corners[3], Color.red); // LowerRight -> LowerLeft
Debug.DrawLine(corners[3], corners[0], Color.red); // LowerLeft -> UpperLeft
// 横
Debug.DrawLine(corners[4], corners[5], Color.red);
// 竖
Debug.DrawLine(corners[6], corners[7], Color.red);
// 视口覆盖的区域
Debug.DrawLine(screenViewTrapezoid[0], screenViewTrapezoid[1], Color.yellow); // UpperLeft -> UpperRight
Debug.DrawLine(screenViewTrapezoid[1], screenViewTrapezoid[2], Color.yellow); // UpperRight -> LowerRight
Debug.DrawLine(screenViewTrapezoid[2], screenViewTrapezoid[3], Color.yellow); // LowerRight -> LowerLeft
Debug.DrawLine(screenViewTrapezoid[3], screenViewTrapezoid[0], Color.yellow); // LowerLeft -> UpperLeft
#endif
}
/// <summary>
/// 对移动偏移量进行验证,超出边界要做相关处理
/// <param name="nearBorder">如果超出边界是否修正为离边界最近的偏移而不是直接变为0</param>
/// </summary>
private Vector3 WrapPosInRect(Vector3 transOff, bool nearBorder = false)
{
var currPos = this.moveTarget.transform.position;
var moveTargetPos = currPos + transOff;
var st = this.CheckScreenCornerIsOutBounds(moveTargetPos);
if (st == 0) return transOff;
if (nearBorder == false)
{
var outX = (st & 1) != 0 || (st & 2) != 0 || (st & 4) != 0 || (st & 8) != 0;
var outZ = (st & 16) != 0 || (st & 32) != 0 || (st & 64) != 0 || (st & 128) != 0;
if (outX) transOff.x = 0;
if (outZ) transOff.z = 0;
}
else
{
transOff = this.WrapTransOff(moveTargetPos, st, transOff);
}
return transOff;
}
private Vector3 WrapTransOff(Vector3 moveTargetPos, int st, Vector3 transOff){
var screenViewTrapezoid = this.GetScreenCornersPosInWorld();
var _p0 = (moveTargetPos + this.moveTargetLocalBounds[0]);
var _p1 = (moveTargetPos + this.moveTargetLocalBounds[1]);
var _p2 = (moveTargetPos + this.moveTargetLocalBounds[2]);
var _p3 = (moveTargetPos + this.moveTargetLocalBounds[3]);
// 移动后视口各个位置与边界的差,就是transOff应该再次偏移的量
// 经过偏移后使得 moveTargetPos+transOff 被限定在边界范围内
if ((st & 1) != 0) transOff.x += screenViewTrapezoid[0].x - _p0.x;
else if ((st & 2) != 0) transOff.x += screenViewTrapezoid[1].x - _p1.x;
else if ((st & 4) != 0) transOff.x += screenViewTrapezoid[2].x - _p2.x;
else if ((st & 8) != 0) transOff.x += screenViewTrapezoid[3].x - _p3.x;
if ((st & 16) != 0) transOff.z += screenViewTrapezoid[0].z - _p0.z;
else if ((st & 32) != 0) transOff.z += screenViewTrapezoid[1].z - _p1.z;
else if ((st & 64) != 0) transOff.z += screenViewTrapezoid[2].z - _p2.z;
else if ((st & 128) != 0) transOff.z += screenViewTrapezoid[3].z - _p3.z;
return transOff;
}
/// 检查可视区域(ScreenCorners)是否超出边界
private int CheckScreenCornerIsOutBounds(Vector3 moveTargetPos)
{
var screenViewTrapezoid = this.GetScreenCornersPosInWorld();
var _p0 = (moveTargetPos + moveTargetLocalBounds[0]);
var _p1 = (moveTargetPos + moveTargetLocalBounds[1]);
var _p2 = (moveTargetPos + moveTargetLocalBounds[2]);
var _p3 = (moveTargetPos + moveTargetLocalBounds[3]);
int st = 0; // 00000000,用8个位来代表4个角的x,z状态
if (_p0.x >= screenViewTrapezoid[0].x) st |= 1;
if (_p1.x <= screenViewTrapezoid[1].x) st |= 2;
if (_p2.x <= screenViewTrapezoid[2].x) st |= 4;
if (_p3.x >= screenViewTrapezoid[3].x) st |= 8;
if (_p0.z <= screenViewTrapezoid[0].z) st |= 16;
if (_p1.z <= screenViewTrapezoid[1].z) st |= 32;
if (_p2.z >= screenViewTrapezoid[2].z) st |= 64;
if (_p3.z >= screenViewTrapezoid[3].z) st |= 128;
return st;
}
/// 获取视口的边界. 上左-上右-下右-下左
private Vector3[] GetCorners(float distance)
{
Vector3[] corners = new Vector3[8];
float halfFOV = (mainCamera.fieldOfView * 0.5f) * Mathf.Deg2Rad;
float aspect = mainCamera.aspect;
float halfHeight = distance * Mathf.Tan(halfFOV);
float halfWidth = halfHeight * aspect;
var tx = this.transform;
// UpperLeft
corners[0] = tx.position - (tx.right * halfWidth);
corners[0] += tx.up * halfHeight;
corners[0] += tx.forward * distance;
// UpperRight
corners[1] = tx.position + (tx.right * halfWidth);
corners[1] += tx.up * halfHeight;
corners[1] += tx.forward * distance;
// LowerRight
corners[2] = tx.position + (tx.right * halfWidth);
corners[2] -= tx.up * halfHeight;
corners[2] += tx.forward * distance;
// LowerLeft
corners[3] = tx.position - (tx.right * halfWidth);
corners[3] -= tx.up * halfHeight;
corners[3] += tx.forward * distance;
// 横
corners[4] = tx.position + Vector3.zero;
corners[4] -= tx.right * halfWidth;
corners[4] += tx.forward * this.camDistance;
corners[5] = tx.position + Vector3.zero;
corners[5] += tx.right * halfWidth;
corners[5] += tx.forward * this.camDistance;
// 竖
corners[6] = tx.position + Vector3.zero; ;
corners[6] -= tx.up * halfHeight;
corners[6] += tx.forward * this.camDistance;
corners[7] = tx.position + Vector3.zero;
corners[7] += tx.up * halfHeight;
corners[7] += tx.forward * this.camDistance;
return corners;
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(CameraViewFixedCam))]
public class DCGCameraViewFixedCamEditor : Editor
{
private Vector3 lookAtPos = new Vector3(269f, 36.8f, 299f);
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
var scview = this.target as DCG.CameraViewFixedCam;
GUILayout.Space(5);
if (GUILayout.Button("跳转到moveTarget", GUILayout.Height(25)))
{
scview.lerpMove = true;
}
GUILayout.Space(5);
lookAtPos = EditorGUILayout.Vector3Field("LookAt坐标点", lookAtPos);
if (GUILayout.Button("LookAtLocal", GUILayout.Height(25)))
{
scview.LookAtLocal(lookAtPos); //new Vector3(122.4f, 5.3f, 132.2f)
// scview.LookAt(GameObject.Find("FarmBlock"));
}
}
}
#endif
主要说明一下边界的检查处理,如图:
- 蓝色线条表示地图可视边界
- 红色表示视锥截面(视锥在指定距离下的截面,本例中即是视锥在摄像机与地图距离上的截面)
- 绿色表示摄像机中心射线
- 灰色是视锥边缘线
- 黄色只是边界垂直线的示例以便显示更直观
- 此处地图应该是:平行于Z轴,在有一定Y高度,带有collider box(box高度不能为0)的gameobject.
是否在边界内也主要是通过判断灰色边缘线在地图上的hitPoint是否在边界内来得到的.
观察以下几张图片:
旋转后视锥上侧边界:
旋转后视锥下侧边界:
在旋转镜头后,如果不做处理,会出现上述图中的情况: 浅蓝色的虚线就是视锥多看到的边界之外的情况. 通过代码中的WrapPosInRect()方法处理后就可以限制地图位置,使其在拖动时视锥边缘不超过指定边界,处理后:
上边缘:
下边缘:
上面2个效果是在摄像机Y轴不旋转的情况下使用Scene窗口的正交模式查看的,Y轴旋转得到如下效果:
Scale()方法中,在处理了缩放后又进行了一次检查,用来确定缩放后,视锥边缘是否超出范围了(比如先拖动到边界,再在屏幕边缘进行缩小操作),如果不处理,就又会出现超出边界的的情况.
一般情况下,最好使可视控制边界不要超过地图的碰撞盒大小,如下图:
因为如果大小比较匹配,在缩放时可能出现视锥边界无法检测到hitpoint的情况,如下图:
无法精确解决(除非自行进行三角函数运算),GetScreenCornersPosInWorld()中进行了一次简单处理, 但依然会使得WrapPosInRect()中(else部分)出现偏差.