Unity 基于正交投影实现地图缩放功能

一、要实现的功能

1. 鼠标停留在某一点,向上滚滑轮,地图放大,并逐渐聚焦于该点

2. 鼠标向下滚滑轮,地图缩小直到恢复到原来的大小

二、相关API

1. 放大和缩小地图需要鼠标滚轮,这这里用到的是

Input.mouseScrollDelta.y  //默认等于0,向上滑大于0,向下滑小于0

 2. 这里会用到正交相机,将Camera下的投影视图(Projection)选为正交投影

当我们点击相机时就能看到相机的可视范围为一个白边围成的矩形

 

 其中的Size表示高度一半占多少个单位,

调整Size的大小时,可视范围会等比例的放大或缩小,这正是实现地图缩放的关键,在代码中调用需要用到下面这个API

Camera.main.orthographicSize

三、功能分析以及代码实现

        正如上面所说,减小Size,可视范围会变小,但是我们的游戏画面的大小是不变的,那么可视范围内的物体就会相对的变大,反过来也一样

 所以放大和缩小地图的代码是统一的,我们先实现一下这部分

Camera c = Camera.main;
float scrollSpeed = 1;//单次滚动滑轮缩放的变化量
public Vector2 range;

void Update()
{
    if(Input.mouseScrollDelta.y != 0)
    {
        c.orthographicSize -= Input.mouseScrollDelta.y * scrollSpeed;//向上滑视野范围变小
        c.orthographicSize = Mathf.Clamp(c.orthographicSize, range.x, range.y);//控制Size的范围
    }
}

1. 放大地图

        放大地图时相机需要逐渐靠近目标,目标是当前鼠标位置,也就是说目标是随着鼠标位置变换时时变化的。我这里考虑的是相机一边缩放一边朝着目标移动。这里需要注意一个问题(这里很关键),当我们指定地图上的目标时,目标的位置是由鼠标的屏幕坐标转成的世界坐标,屏幕会朝着这个世界坐标移动,由于我们的鼠标是在屏幕上的,当屏幕移动时,即使鼠标和屏幕的相对位置没变,鼠标转成的空间坐标也会改变(除非一种情况),相当于鼠标又指向了新的位置,原来的目标会远离鼠标,那么就要重新指定目标,大概逻辑如下

init:
c = Camera.main
targetPos = c.ScreenToWorldPoint(Input.mousePosition) = mouse.WorldPos

update:
mouse.screenPos -> targetPos => c.WorldPos change => mouse.WorldPos change => targetPos != mouse.WorldPos => mouse.screenPos -> targetPos => ...

这样显然是很不自然的,而且会出现屏幕抖动的情况。

        事实上空间的缩放和平移被称为仿射变换,所以屏幕上的点在世界坐标系下的缩放和平移可以看成从平面到平面的映射

T:\mathbb{R}^2\rightarrow \mathbb{R}^2\\ ~~~~~~~v_1 \mapsto v_2

学过泛函分析的同学肯定知道这显然是一种压缩映射,压缩映射一定会有一个不动点,即 

T(x) =x

 那么如果我们令目标点是这个不动点,那么问题自然就解决了,因为当我们鼠标指向不动点时,屏幕虽然在动,但是这时鼠标的世界坐标并不会动(这就是我上问题提到的例外的情况),那么指向的仍然是一开始的目标,所以上面的逻辑变成了

update:
mouse.screenPos -> targetPos => c.WorldPos change => mouse.WorldPos fixed => targetPos == mouse.WorldPos => mouse.screenPos fixed

        那么我们现在就要找到使得目标点是不动点的压缩映射。其实很简单,就是初中学过的相似(其实我一开始想到的这个,上面那些高大上的东西是后来编的)

我们只需要考虑屏幕中心在世界坐标系下的运动就行了,由于缩放是匀速变化的,那么显然中心的移动也是沿直线匀速运动的,最后达到目标位置,所以有

v = \frac{d}{t}\\ t=\frac{Size}{scrollSpeed}

这里我的scrollSpeed取1,即

v=\frac{d}{Size}

 其中d表示开始时中心到目标的距离,Size表示开始时屏幕的大小,由相似关系可知每时每刻d和Size的比是不变的,又因为我们的鼠标是实时定位的,所以我们最好把公式写成带t的形式,即

v(t) = \frac{d(t)}{Size(t)}

不然还要检测鼠标是否移动,在鼠标移动时改变初值,比较麻烦。

        上面把原理说完了,接下来代码就比较简单了

if(Input.mouseScrollDelta.y > 0)
{
    //如果达到最小范围,则不再移动
    if (c.orthographicSize > minSize)
    {
        Vector3 mousePos = c.ScreenToWorldPoint(Input.mousePosition);
        Vector3 direction = new Vector3(mousePos.x, mousePos.y, 0) - new Vector3(transform.position.x, transform.position.y, 0);//包含方向和距离
        c.transform.Translate(direction/c.orthographicSize);
    }
}

2. 缩小地图

        这个相比上面要简单许多,因为不管屏幕当前的中心在什么位置,我的最终目标一定是回到世界坐标系的原点,所以只需要在缩放的同时屏幕中心匀速运动到原点就行了,公式如下

v(t) = \frac{d(t)}{maxSize-Size(t)}

所以代码如下

else if(c.orthographicSize < maxSize)
{
    Vector3 direction = new Vector3(0, 0, 0) - new Vector3(transform.position.x, transform.position.y, 0);
    c.transform.Translate(direction / (maxSize-c.orthographicSize));
}

四、完整代码

public class CameraC : MonoBehaviour
{

    public Vector2 range;
    public float scrollSpeed;
    
    Camera c;

    void Start()
    {
        c = Camera.main;
    }

    void Update()
    {
        if(Input.mouseScrollDelta.y != 0)
        {
            c.orthographicSize -= Input.mouseScrollDelta.y * scrollSpeed;
            c.orthographicSize = Mathf.Clamp(c.orthographicSize, range.x, range.y);
            Vector3 direction;
            if(Input.mouseScrollDelta.y > 0)
            {
                if (c.orthographicSize > range.x)
                {
                    Vector3 mousePos = c.ScreenToWorldPoint(Input.mousePosition);
                    direction = new Vector3(mousePos.x, mousePos.y, 0) - new Vector3(transform.position.x, transform.position.y, 0);
                    c.transform.Translate(direction/c.orthographicSize);
                }
            }
            else if(c.orthographicSize < range.y)
            {
                direction = Vector3.zero - new Vector3(transform.position.x, transform.position.y, 0);
                c.transform.Translate(direction / (54-c.orthographicSize));
            }
        }
    }
}

五、结束

        以上就是我实现地图缩放的方法,除了API,其他都是自己想的,如果有什么更方便的方法欢迎友好交流。

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值