🔴离散网格地图制作(类似明日方舟基建)
前置:屏幕适配
我们在UI里面可以很轻松的完成屏幕适配。可是在2d场景中,并没有类似锚点的功能。那么我们来讲解一下到底要怎么完成屏幕适配。
一、正交相机(Orthographic Camera)
正交相机是什么?
✅ 去掉透视效果的摄像机
✅ 所见即世界坐标单位
✅ 物体远近不影响大小(Orthographic 下没有"近大远小")
3个核心知识点(重点)
知识点 1:Size 决定"世界高度"
csharp
Camera.orthographicSize = 5;
垂直方向能看到 10 个单位的世界
↑ 5
↓
↓ 5

调大一点

调小一点

知识点 2:宽度是"算出来的"
可见世界高度 = orthographicSize × 2
可见世界宽度 = 可见世界高度 × aspect
(aspect代表屏幕宽高比)
csharp
float worldWidth = Camera.main.orthographicSize * 2f * Camera.main.aspect;

aspect是不可以随便改的哦!就是这个东西:
16:9的图像宽高比,可以看到上下有很宽的黑边

自由宽高比

知识点 3:Unity 单位 ≠ 像素
Pixels Per Unit = 100
✅ Sprite 大小只和 PPU 有关
✅ 和分辨率无关
综上,我们可以看出,如果你的游戏是以16:9的比例开发的,但是如果用户用了一个1:1的屏幕打开,那么左右两边的画面必然有大量缺失!
二、关于屏幕适配最大的困扰
如果我的游戏是基于16:9的长宽比开发的,那用户用1:1的屏幕打开不就炸了吗?
✅ 是的------如果你"一切布局都死死按 16:9 摆",一旦变成 1:1,画面一定会"毁"。
但关键在于:
❗ "毁"是正常的,也是可控的
❗ 商业游戏不是"避免毁",而是决定怎么毁
为什么商业游戏还能"看起来没问题"?
因为它们从一开始就接受了这个事实,并做了三件事:
✅ 第一件事:选一个"主比例",其它比例都算异常

主比例 = 完美体验
其它比例 = 允许牺牲一部分体验
✅ 第二件事:决定"牺牲哪一边"

✅ 第三件事:布局时根本不把东西贴边
综上,游戏画面(2D 场景 / Sprite)的适配,不像 UI 那样让元素随屏幕动态缩放锚定;
而是先决定一个"设计画面宽高",再通过调整相机或留余量,决定牺牲哪一部分画面来适配不同屏幕。
🔴摄像机控制画面的偏移和缩放
画面偏移我就不说了,就是摄像机位置的移动。重点讲一下缩放的原理,我们刚才讲了orthographicSize。
orthographicSize决定的是:摄像机在"垂直方向"能看到多少个世界单位(Unity Unit)
✅ 它不是缩放系数
✅ 它不是分辨率
✅ 它是世界空间的裁剪高度
为什么改 size 看起来像"画面缩放"?
世界里的东西大小没变
相机"看多高"变了
所以画面看起来变大或变小
✅ 这是视觉缩放,不是几何缩放
为什么 size 不能直接叫"Zoom"?
Unity 故意不给它起名叫 zoom(缩放),是为了提醒你:
⚠️ 它不是"放大物体",而是"放大你看到的世界"
PPU (Pixels Per Unit)
PPU是什么?
PPU 代表每世界单位包含多少个纹理像素(Texture Pixels)
Texture n. 质地;口感;手感;
Texture Pixels n.纹理像素
如何修改PPU
PPU(Pixels Per Unit)的修改位置在图片资源的导入设置(Import Settings)里
在 Project 项目面板中,选中你要修改的 Sprite 图片(注意是图片资源本身,不是场景里的 Sprite 对象)

PPU 越大 → Sprite 在世界里越小
PPU 越小 → Sprite 在世界里越大
这个原理非常简单,但极其重要。
假设你有一张贴图:
图片尺寸:200 × 200 像素
✅ PPU = 100
200 ÷ 100 = 2
👉 这张 Sprite 在世界里占 2 × 2 个单位
✅ PPU = 200
200 ÷ 200 = 1
👉 这张 Sprite 在世界里占 1 × 1 个单位
✅ PPU = 50
200 ÷ 50 = 4
👉 这张 Sprite 在世界里占 4 × 4 个单位
修改图片缩放和修改PPU的区别

简单实现
Hierarchy中添加以下对象,Rooms,Room_A,CameraaController都创建空对象。

在Room_A中添加SpriteRender和Polygon Colider(不规则碰撞体)

创建以下脚本
Room.cs
csharp
using UnityEngine;
public class Room : MonoBehaviour
{
[Header("Camera Settings")]
public float cameraSize = 5f; //聚焦后的正交摄像机size
/// <summary>
/// 动态获取room的位置以确定摄像机要对准的位置
/// </summary>
public Vector3 CameraTargetPos =>
new Vector3(
transform.position.x,
transform.position.y,
-10f
);
}
RoomClick.cs
csharp
using UnityEngine;
public class RoomClick : MonoBehaviour
{
private Room room;
void Awake()
{
room = GetComponent<Room>();
}
void OnMouseDown() //OnMouseDown()是 Unity 内置的生命周期函数
{
Debug.Log($"[RoomClick] Clicked = {gameObject.name}, pos={transform.position}");
CameraController.Instance.FocusRoom(room);
}
}
CameraController.cs
csharp
using UnityEngine;
public class CameraController : MonoBehaviour
{
public static CameraController Instance;
[Header("Camera")]
public Camera cam;
[Header("Movement")]
public float moveSpeed = 5f;
public float zoomSpeed = 5f;
private Vector3 targetPos;
private float targetSize;
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
if (cam == null)
cam = Camera.main;
}
void Start()
{
targetPos = cam.transform.position;
targetSize = cam.orthographicSize;
}
void Update()
{
//Vector3.Lerp和 Mathf.Lerp的思想完全一样,只是作用对象从「一个 float」变成了「一个三维向量」。
cam.transform.position = Vector3.Lerp(
cam.transform.position,
targetPos,
Time.deltaTime * moveSpeed
);
//Lerp = Linear Interpolation(线性插值)
//Lerp的作用是在两个值之间,按一个比例取中间值,因为orthographicSize一只在变,会一直触发Update,直到targetSize与orthographicSize相等
cam.orthographicSize = Mathf.Lerp(
cam.orthographicSize, //起始值
targetSize, //目标值
Time.deltaTime * zoomSpeed //插值比例(0~1)
);
}
/// <summary>
/// 聚焦单个房间
/// </summary>
public void FocusRoom(Room room)
{
if (room == null) return;
targetPos = room.CameraTargetPos;
targetSize = room.cameraSize;
}
/// <summary>
/// 回到全局视图
/// </summary>
public void FocusGlobal()
{
targetPos = new Vector3(0f, 0f, -10f);
targetSize = 10f;
}
}
Room.cs和RoomClick.cs都挂载在Room_A,这两个代码最好不要合并(解耦),CameraController 挂载在CameraController 上面。在UI中添加一个退回全屏按钮,把FocusGlobal挂载上去。
效果:

点击任意一个room缩放

点击button退回全局
