# 前言

前些天做大地图用到过一次,要求点大地图哪儿,就把玩家传送至点击的相应世界位置。而且这张大地图是事先截取好的图片。
要说来的话,这功能也还是 “前任” 留下来的,新公司之前的程序走掉之后,项目就留在那儿了,我来之后,才又开工的样子。
本来以为这地图应该稍稍改改就好了,结果鼠标点上去图标乱飞,人也不见了踪影....
定睛一看,不但地图用的是 UE4 项目中截取的(这项目之前公司还用 UE4 做了一版,不过说是因为有些功能实现不了,才又换到 Unity 的),其中代码注意了下,也是 UE4 中类似的算法提过来的样子。

本以为应该很简单,很快就改好了.... 不过之后大概地点没问题,但是无论怎么改,地图上都有些许误差,看起来就是点了地图上某个位置,但是实际上却偏移了一段。

然后新公司也就我一个程序,只好自个儿琢磨了。
后来折腾了些时候,考虑着:先不说 UE4 中截取的地图跟 Unity 中单位兼不兼容,单单是之前截取地图的比例我也不知道。
便思虑着是否确实是地图比例 (长宽,是否对得上本身的分辨率?) 不对?
于是就想到还是用 Unity 本身来截取大地图试试。

把之前代码里边定义了许多固定坐标、世界长度 (貌似其实不对) 之类的都删了,自个儿重新来,我采用了最简单的一种按比例计算的方式实现,这儿就稍作记录吧。

# 功能

# 描述

我所做的大地图,是事先使用一个 orthographic 相机对场景进行截图,然后保存为一张 PNG 格式的图片作为大地图。

所以在此之前,我们必须要明白一点:orthographicSize。
在 Unity 正交相机模式下,orthographicSize 一个单位等于 Unity 世界 2 个单位,不过仅限于纵向 —— 也就是说纵向的可以显示的范围 = orthographicSize*2。
比如说 orthographicSize=360,那么相机实际可以显示的场景中 720 米的范围 (纵向)。
而横向显示长度,则是据此并与屏幕长宽比例相关。

知道了这一点的话,那么就可以根据这个比例关系,对使用 orthographic 相机截取的地图进行定位了(稍微说一下:因此,若想保持比例正确,且少费功夫,其纵向长度必须保持上述比例,而宽度,则可根据比例 (地图分辨率、屏幕分辨率) 自行换算)。
之前就说了,我是用的最简单的方式 —— 所以不考虑更多的比例关系,主要就是将鼠标点击的位置转换至 UI 中,因为图片、UI 比例一致,所以鼠标在 UI 中的坐标,就可以直接被当做大地图中的实际坐标了。
不大清楚的话,可以这样考虑:UI 的纵向长度为 720,同时代表了 720 米的距离,UI 横向长度为 1024,代表了 1024 米的距离。

地图如下:

地图

# 实现

在我做的项目中,UI 显示的大小为 1024x720,所以截图也要根据这个比例来,为了增加清晰度,截图放大了一倍,即 2048x1440。
orthographicSize 则以实际 UI 的比例为主,所以 orthographicSize=720/2=360.

照上述所言,之前就说了是很简单的方法了,所以定位就很简单了。
主要就是将鼠标点击的位置转至以屏幕中心为准,这样子的话,那么地图比例依照上述,点击位置距离中心点有多远,便可依样转换至世界坐标距离原点 Vector3 (0,0,0) 的坐标。

代码:

s
// 将点击位置映射至中点
        _clickTargetPosition = new Vector2(Screen.width / 2.0f, Screen.height / 2.0f) - _clickMousePos;
        // 三维转二维
        _clickTargetPosition.z = _clickTargetPosition.y;
        //UI 比例缩放
        _clickTargetPosition /= _canvas.scaleFactor;
        _clickTargetPosition = new Vector3(_clickTargetPosition.x, 70, _clickTargetPosition.z);

接下来是截图。
截图代码:

s
[MenuItem("Tools/Capture BigMap")]
    private static void CaptureBigMap()
    {
        Camera ca = new GameObject("[Camera]").AddComponent<Camera>();
        ca.orthographic = true;
        ca.orthographicSize = 360;
        ca.cullingMask &= ~(1 << LayerMask.NameToLayer("Pipe"));
        ca.backgroundColor = Color.black;
        ca.clearFlags = CameraClearFlags.SolidColor;
        ca.transform.position = new Vector3(0, 500, 0);
        ca.transform.eulerAngles = new Vector3(90, 180, 0);
        CaptureCamera(ca, new Rect(0, 0, 2048, 1440), Application.dataPath + "/../BigMap.png");
        DestroyImmediate(ca.gameObject);
    }
    private static void CaptureCamera(Camera camera, Rect rect, string fileName)
    {
        RenderTexture rt = new RenderTexture((int)rect.width, (int)rect.height, -1);
        RenderTexture.active = rt;
        camera.targetTexture = rt;
        camera.Render();
        Texture2D screenShot = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24, false);
        screenShot.ReadPixels(rect, 0, 0);
        screenShot.Apply();
        RenderTexture.active = null;
        byte[] bytes = screenShot.EncodeToPNG();
        System.IO.File.WriteAllBytes(fileName, bytes);
        Debug.Log("截图完成:" + fileName);
    }

需要注意的一点是,截图大小是多少,同时也必须把屏幕的比例设置相同,当然最好、或者说最简单的方法就是直接将屏幕的分辨率设置为截图大小。
如图:

# 结语

这样,按照比例做的图片地图就好了。
因为地图与世界坐标比例基本一致,点哪儿传送至哪儿完全没问题。