简单介绍一种控制相机缩放和平移的方法,包含2D相机和3D相机,在 Update 中实现即可。控制视野部分,采用了射线检测的方法。具体的例子,可以新建一个工程,在场景中做出下图中的结构。

Cube 可有可无,MainCamera 是默认相机,注意 Plane 的 Layer 为 “Plane”,然后在 controller 物体上添加下面的脚本即可:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class BattleController : MonoBehaviour
{

    private int mapRows;
    private int mapCols;

    public Camera MainCamera;

    private readonly Plane refPlaneXZ = new Plane(new Vector3(0, 1, 0), 0);
    private float cameraOffset;

    /// <summary>
    /// 是否使用正交相机
    /// </summary>
    public const bool IsCameraOrthographic = false;

    /// <summary>
    /// 用于碰撞检测的底板
    /// </summary>
    public Transform PlaneGrid;

    /// <summary>
    /// 底板缩放比例,使得没格子的地方也可以点击到
    /// </summary>
    public const int PlaneGridScale = 2;

    /// <summary>
    /// 是否正在移动地图
    /// </summary>
    private bool isMovingMap;

    /// <summary>
    /// 是否按下鼠标
    /// </summary>
    private bool isClickedDown;

    public LayerMask PlaneLayerMask { get; set; }

    /// <summary>
    /// 一个看不到的位置
    /// </summary>
    public readonly Vector3 InvisiblePos = new Vector3 (10000, 10000, 10000);

    /// <summary>
    /// 一个看不到的位置
    /// </summary>
    public readonly Vector2 InvisibleScreenPos = new Vector2 (10000, 10000);

    /// <summary>
    /// 上一次点击的位置
    /// </summary>
    private Vector2 navigateLastPosition;

    private const float MaximumOrthographicSize = 7.4f;
    private const float MinimumOrthographicSize = 3.4f;

    private const float MaximumFOV = 10f;
    private const float MinimumFOV = 5.4f;

    private const float RayCastMaxDistance = 1000;
    private const float NavigateZoomSpeed = 10f;
    private const float MinPinchSpeed = 2.0F;
    private const float MinOffset = 0.3f;

    /// <summary>
    /// 是否正在手机上运行
    /// </summary>
    private bool isMobilePlatform;

    void Awake ()
    {
        MainCamera.orthographic = IsCameraOrthographic;
        if (MainCamera.orthographic) {
            this.transform.position = new Vector3 (21.3f, 30f, -21.3f);
            this.transform.eulerAngles = new Vector3 (45f, -45f, 0f);
            MainCamera.orthographicSize = 6.4f;
            MainCamera.transform.localPosition = Vector3.zero;
            MainCamera.transform.localEulerAngles = Vector3.zero;
        } else {
            this.transform.position = new Vector3 (60f, 60f, -60f);
            this.transform.eulerAngles = new Vector3 (40f, -45f, 0f);
            MainCamera.fieldOfView = 8f;
            MainCamera.transform.localPosition = Vector3.zero;
            MainCamera.transform.localEulerAngles = Vector3.zero;
        }

        isMobilePlatform = (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer);

        PlaneLayerMask = 1 << LayerMask.NameToLayer ("Plane");

        Ray ray = MainCamera.ViewportPointToRay (new Vector2 (0.5f, 0.5f));
        Vector3 center = GetIntersectionPoint (ray);
        cameraOffset = Mathf.Sqrt(center.x * center.x + center.z * center.z);

        mapRows = 40;
        mapCols = 40;
        PlaneGrid.localScale = new Vector3 (mapCols * PlaneGridScale / 10f, 1, mapRows * PlaneGridScale / 10f);
    }

    void Update ()
    {
        // 鼠标按下
        if (Input.GetMouseButtonDown(0))
        {
            isClickedDown = true;
            navigateLastPosition = InvisibleScreenPos;
            isMovingMap = false;
            navigateLastPosition = Input.mousePosition;
        }

        if (isClickedDown)
        {
            if (navigateLastPosition != InvisibleScreenPos)
            {
				if (!isMobilePlatform)
				{
					Vector3 deltaPos = GetDeltaPos(Input.mousePosition, navigateLastPosition);
					if (deltaPos != InvisiblePos && deltaPos.magnitude > MinOffset && !isMovingMap) {
						isMovingMap = true;
					}
					if (isMovingMap) {
						Vector3 cameraWorldPos = MainCamera.transform.position + deltaPos;
						SetCameraPosition (cameraWorldPos);
						navigateLastPosition = Input.mousePosition;
					}
				}
				else
				{
					if (Input.touchCount == 1) {
						Touch touch = Input.GetTouch (0);

						if (touch.phase == TouchPhase.Began)
						{
							navigateLastPosition = touch.position;
						}

						if (touch.phase == TouchPhase.Moved) {
							Vector3 deltaPos = GetDeltaPos(touch.position, navigateLastPosition);
							if (deltaPos != InvisiblePos && deltaPos.magnitude > MinOffset && !isMovingMap) {
								isMovingMap = true;
							}
							if (isMovingMap) {
								Vector3 cameraWorldPos = MainCamera.transform.position + deltaPos;
								SetCameraPosition (cameraWorldPos);
								navigateLastPosition = touch.position;
							}
						}
					}
				}
            }
        }

        // 鼠标弹起
        if (Input.GetMouseButtonUp(0))
        {
            isClickedDown = false;
        }

        // 可以移动物体时,点击城墙边缘时,不允许缩放屏幕
        if (!isMobilePlatform)
        {
            // 鼠标滚轮缩放
			if (IsCameraOrthographic) {
				MainCamera.orthographicSize = Mathf.Clamp (
					MainCamera.orthographicSize + (-1 * NavigateZoomSpeed * Input.GetAxis ("Mouse ScrollWheel")),
					MinimumOrthographicSize, MaximumOrthographicSize);
				SetCameraPosition (MainCamera.transform.position);
			} else {
				MainCamera.fieldOfView = Mathf.Clamp (
					MainCamera.fieldOfView + (-1 * NavigateZoomSpeed * Input.GetAxis ("Mouse ScrollWheel")),
					MinimumFOV, MaximumFOV);
				SetCameraPosition (MainCamera.transform.position);
			}
        }
        else
        {
            // 屏幕缩放
            if (Input.touchCount == 2) {

                Touch touch0 = Input.GetTouch (0);
                Touch touch1 = Input.GetTouch (1);
                if (touch0.phase == TouchPhase.Moved || touch1.phase == TouchPhase.Moved) {

                    Vector2 curDist = touch0.position - touch1.position;
                    Vector2 prevDist = (touch0.position - touch0.deltaPosition) - (touch1.position - touch1.deltaPosition);
                    float touchDelta = curDist.magnitude - prevDist.magnitude;
                    float speedTouch0 = touch0.deltaPosition.magnitude / touch0.deltaTime;
                    float speedTouch1 = touch1.deltaPosition.magnitude / touch1.deltaTime;

                    if (speedTouch0 > MinPinchSpeed || speedTouch1 > MinPinchSpeed) {
                        if (IsCameraOrthographic) {
                            MainCamera.orthographicSize -= touchDelta * 0.03f;
                            MainCamera.orthographicSize = Mathf.Clamp (MainCamera.orthographicSize, MinimumOrthographicSize, MaximumOrthographicSize);
                        } else {
                            MainCamera.fieldOfView -= touchDelta * 0.03f;
                            MainCamera.fieldOfView = Mathf.Clamp (MainCamera.fieldOfView, MinimumFOV, MaximumFOV);
                        }
                        // 缩放屏幕时,设置为正在移动地图
                        isMovingMap = true;
                    }
                    SetCameraPosition (MainCamera.transform.position);
                }

                if (touch0.phase == TouchPhase.Ended) {
                    navigateLastPosition = touch1.position;
                } else if (touch1.phase == TouchPhase.Ended) {
                    navigateLastPosition = touch0.position;
                }
            }
        }
    }

	public void MoveCameraToPosition (Vector3 targetPos)
    {
        Ray cameraCenterRay = MainCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f));
        RaycastHit cameraHit;
        if (Physics.Raycast(cameraCenterRay, out cameraHit, RayCastMaxDistance, PlaneLayerMask)) {
            Vector3 camViewCenter = cameraHit.point;
            Vector3 camDestPos = targetPos- camViewCenter;
            camDestPos.y = 0;
            Vector3 cameraWorldPos = MainCamera.transform.position + camDestPos;
            SetCameraPosition (cameraWorldPos);
        }
    }

    /// <summary>
    /// 通过射线获取屏幕上两个坐标的世界坐标的偏移量
    /// </summary>
    Vector3 GetDeltaPos (Vector2 mousePos, Vector2 lastPos)
    {
        Ray ray = MainCamera.ScreenPointToRay (mousePos);
        RaycastHit hitInfo;
        if (Physics.Raycast (ray, out hitInfo, RayCastMaxDistance, PlaneLayerMask)) {
            Ray lastRay = MainCamera.ScreenPointToRay (lastPos);
            RaycastHit lastHitInfo;
            if (Physics.Raycast (lastRay, out lastHitInfo, RayCastMaxDistance, PlaneLayerMask)) {
                Vector3 deltaPos = hitInfo.point - lastHitInfo.point;
                deltaPos.y = 0;
                deltaPos *= -1;
                return deltaPos;
            }
        }
        return InvisiblePos;
    }

    /// <summary>
    /// 限制视野范围
    /// </summary>
    void SetCameraPosition (Vector3 worldPos)
    {
        if (IsCameraOrthographic) {
            Vector3 localPos = transform.InverseTransformPoint(worldPos);
            float halfHeight = MainCamera.orthographicSize;
            float halfWidth = halfHeight * MainCamera.aspect;
            Vector3 cameraLocalPos = localPos;

            float min = (float)Mathf.Min (mapRows, mapCols);
            float max = (float)Mathf.Max (mapRows, mapCols);
            float halfSqrtValue = Mathf.Sqrt (min * min + max * max) * 0.5f;

            float angle = Mathf.Atan (min / max);
            float leftAngle = 45 - angle;
            float length = halfSqrtValue * Mathf.Cos (leftAngle);

            float lengthX = length;
            float x1 = halfWidth - lengthX;
            float x2 = lengthX - halfWidth;
            cameraLocalPos.x = Mathf.Clamp (cameraLocalPos.x, Mathf.Min (x1, x2), Mathf.Max (x1, x2));

            float lengthY = length / Mathf.Sqrt (2);
            float y1 = halfHeight - lengthY;
            float y2 = lengthY - halfHeight;
            cameraLocalPos.y = Mathf.Clamp (cameraLocalPos.y, Mathf.Min (y1, y2), Mathf.Max (y1, y2));

            cameraLocalPos.z = 0;
            MainCamera.transform.localPosition = cameraLocalPos;
        } else {
            Vector3 localPos = transform.InverseTransformPoint(worldPos);

            float min = (float)Mathf.Min (mapRows, mapCols);
            float max = (float)Mathf.Max (mapRows, mapCols);
            float halfSqrtValue = Mathf.Sqrt (min * min + max * max) * 0.5f;

            float angle = Mathf.Atan (min / max);
            float leftAngle = 45 - angle;
            float length = halfSqrtValue * Mathf.Cos (leftAngle);

            Vector3 camCenter = GetIntersectionPoint(MainCamera.ScreenPointToRay(new Vector3(Screen.width * 0.5f, Screen.height * 0.5f, 0)));
            Vector3 camRight = GetIntersectionPoint(MainCamera.ScreenPointToRay(new Vector3(Screen.width, Screen.height * 0.5f, 0)));
            Vector3 camLeft = GetIntersectionPoint(MainCamera.ScreenPointToRay(new Vector3(0, Screen.height * 0.5f, 0)));
            Vector3 camUp = GetIntersectionPoint(MainCamera.ScreenPointToRay(new Vector3(Screen.width * 0.5f, Screen.height, 0)));
            float offset = cameraOffset;

            float halfCameraWidth = Vector3.Distance (camRight, camLeft) * 0.5f;
            float halfCameraHeight = Vector3.Distance (camUp, camCenter);

            float x1 = halfCameraWidth - length;
            float x2 = length - halfCameraWidth;
            localPos.x = Mathf.Clamp (localPos.x, Mathf.Min (x1, x2), Mathf.Max (x1, x2));

            float y1 = halfCameraHeight - length + offset;
            y1 = y1 / Mathf.Sqrt (2);
            float y2 = length - halfCameraHeight + offset;
            y2 = y2 / Mathf.Sqrt (2);
            localPos.y = Mathf.Clamp (localPos.y, Mathf.Min (y1, y2), Mathf.Max (y1, y2));

            float z1 = halfCameraHeight - length + offset;
            z1 = z1 / Mathf.Sqrt (2);
            float z2 = length - halfCameraHeight + offset;
            z2 = z2 / Mathf.Sqrt (2);
            localPos.z = Mathf.Clamp (localPos.z, Mathf.Min (z1, z2), Mathf.Max (z1, z2));

            MainCamera.transform.localPosition = localPos;
        }
    }

    /// Method for retrieving the intersection-point between the given ray and the ref plane.
    public Vector3 GetIntersectionPoint(Ray ray) {
        float distance = 0;
        bool success = refPlaneXZ.Raycast(ray, out distance);
        if (success == false) {
            Debug.LogError("Failed to compute intersection between camera ray and reference plane.");
        }
        return (ray.origin + ray.direction * distance);
    }
}