背景
做移动端游戏最头疼的就是屏幕适配。不同设备尺寸千奇百怪,iPhone SE 是 640×1136,iPhone X 是 1125×2436,还有各种安卓奇葩比例。Cocos Creator 提供了好几个分辨率相关的 API,但到底该用哪个?我刚开始也是一头雾水,写这篇文章记录下我的理解和踩过的坑。
核心概念
坐标系统对比
1 2 3 4 5 6 7 8 9 10 11 12
| ┌─────────────────────────────────────────┐ │ 屏幕坐标系 │ │ (0,0) ───────────────→ X+ │ │ │ │ │ │ ┌─────────────┐ │ │ │ │ │ │ │ │ │ 游戏内容 │ │ │ │ │ │ │ │ │ └─────────────┘ │ │ ↓ │ │ Y+ │ └─────────────────────────────────────────┘
|
关键术语
| 术语 |
说明 |
对应 API |
| 设计分辨率 |
开发时设定的参考分辨率 |
getDesignResolutionSize |
| 屏幕分辨率 |
设备的实际像素分辨率 |
getFrameSize |
| 可见区域 |
实际能看到的游戏内容区域 |
getVisibleSize |
| 视图大小 |
以设计像素为单位的画布大小 |
getWinSize |
核心 API 对比
1. getDesignResolutionSize - 获取设计分辨率
1 2 3 4
| getDesignResolutionSize: function () { return cc.view.getDesignResolutionSize(); }
|
说明:
- 这是在 Cocos Creator 编辑器 Canvas 中设置的尺寸
- 开发时所有 UI 布局都基于此分辨率
- 无论实际设备分辨率如何,逻辑坐标都基于此
使用场景:
1 2 3 4 5 6 7 8
| let designSize = cc.view.getDesignResolutionSize(); let scaleX = designSize.width / 720; let scaleY = designSize.height / 1280;
this.node.scaleX = scaleX; this.node.scaleY = scaleY;
|
2. getFrameSize - 获取屏幕分辨率
1 2 3 4
| getFrameSize: function () { return cc.view.getFrameSize(); }
|
说明:
- 这是设备屏幕的实际像素尺寸
- 手机屏幕或浏览器窗口的实际大小
- 包含状态栏、导航栏等系统 UI 占据的空间
使用场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let frameSize = cc.view.getFrameSize(); let totalPixels = frameSize.width * frameSize.height;
if (totalPixels > 2073600) { cc.loader.loadResDir("hd/textures", (err, assets) => { }); } else { cc.loader.loadResDir("textures", (err, assets) => { }); }
|
3. getVisibleSize - 获取可见区域
1 2 3 4
| getVisibleSize: function () { return cc.view.getVisibleSize(); }
|
说明:
- 返回实际可见的游戏区域大小
- 考虑到安全区域(刘海屏、圆角屏)
- 这是玩家实际能看到的游戏内容范围
使用场景:
1 2 3 4 5 6 7
| let visibleSize = cc.view.getVisibleSize(); let visibleOrigin = cc.view.getVisibleOrigin();
this.topButton.x = visibleOrigin.x + visibleSize.width / 2; this.topButton.y = visibleOrigin.y + visibleSize.height - 50;
|
4. getWinSizeInPixels - 获取像素级视图大小
1 2 3 4
| getWinSizeInPixels: function () { return cc.director.getWinSizeInPixels(); }
|
说明:
- 返回以像素为单位的可见区域大小
- 与 getVisibleSize 类似,但以像素为单位
5. getWinSize - 获取视图大小
1 2 3 4
| getWinSize: function () { return cc.winSize; }
|
说明:
- 这是 Cocos Creator 中最常用的属性
- 返回以设计像素为单位的画布大小
- 等同于
cc.director.getWinSize()
分辨率适配策略
固定高度适配
1 2 3 4 5 6
| cc.view.setDesignResolutionSize( 720, 1280, cc.ResolutionPolicy.FIXED_HEIGHT );
|
效果:
- 高度始终填满屏幕
- 宽度可能裁剪或留白
- 适合竖屏游戏
固定宽度适配
1 2 3 4 5
| cc.view.setDesignResolutionSize( 720, 1280, cc.ResolutionPolicy.FIXED_WIDTH );
|
效果:
- 宽度始终填满屏幕
- 高度可能裁剪或留白
- 适合横屏游戏
适配策略对比
| 策略 |
特点 |
适用场景 |
| FIXED_WIDTH |
宽度固定,高度自适应 |
横屏游戏 |
| FIXED_HEIGHT |
高度固定,宽度自适应 |
竖屏游戏 |
| SHOW_ALL |
显示全部,可能有黑边 |
需要完整显示内容 |
| NO_BORDER |
无黑边,可能裁剪 |
允许裁剪边缘内容 |
| EXACT_FIT |
拉伸填充,可能变形 |
不推荐 |
实际应用示例
1. 背景图片全屏适配
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
| const { ccclass, property } = cc._decorator;
@ccclass export default class BackgroundAdapter extends cc.Component {
@property(cc.Sprite) bgSprite: cc.Sprite = null;
onLoad() { this.adaptBackground(); }
adaptBackground() { let designSize = cc.view.getDesignResolutionSize();
let frameSize = cc.view.getFrameSize();
let designRatio = designSize.width / designSize.height; let frameRatio = frameSize.width / frameSize.height;
if (frameRatio > designRatio) { let scale = frameSize.width / designSize.width; this.bgSprite.node.scale = scale * (designSize.height / this.bgSprite.node.height); } else { let scale = frameSize.height / designSize.height; this.bgSprite.node.scale = scale * (designSize.width / this.bgSprite.node.width); } } }
|
2. UI 安全区域适配
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
| const { ccclass, property } = cc._decorator;
@ccclass export default class SafeAreaAdapter extends cc.Component {
@property(cc.Node) topBar: cc.Node = null;
@property(cc.Node) bottomBar: cc.Node = null;
@property(cc.Node) content: cc.Node = null;
onLoad() { this.adaptToSafeArea(); }
adaptToSafeArea() { let visibleSize = cc.view.getVisibleSize(); let visibleOrigin = cc.view.getVisibleOrigin();
let designSize = cc.view.getDesignResolutionSize();
if (this.topBar) { let topMargin = designSize.height - visibleSize.height - visibleOrigin.y; this.topBar.y = designSize.height / 2 - topMargin - this.topBar.height / 2; }
if (this.bottomBar) { let bottomMargin = visibleOrigin.y; this.bottomBar.y = -designSize.height / 2 + bottomMargin + this.bottomBar.height / 2; }
if (this.content) { let safeHeight = visibleSize.height - this.topBar.height - this.bottomBar.height; this.content.height = safeHeight; } } }
|
3. 横竖屏适配
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
| const { ccclass, property } = cc._decorator;
@ccclass export default class OrientationAdapter extends cc.Component {
@property(cc.Node) portraitLayout: cc.Node = null;
@property(cc.Node) landscapeLayout: cc.Node = null;
onLoad() { this.checkOrientation(); cc.view.setResizeCallback(() => { this.checkOrientation(); }); }
checkOrientation() { let frameSize = cc.view.getFrameSize(); let isLandscape = frameSize.width > frameSize.height;
if (this.portraitLayout) { this.portraitLayout.active = !isLandscape; }
if (this.landscapeLayout) { this.landscapeLayout.active = isLandscape; }
if (isLandscape) { cc.view.setDesignResolutionSize( 1280, 720, cc.ResolutionPolicy.FIXED_HEIGHT ); } else { cc.view.setDesignResolutionSize( 720, 1280, cc.ResolutionPolicy.FIXED_WIDTH ); } } }
|
4. 字体大小适配
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
| const { ccclass, property } = cc._decorator;
@ccclass export default class FontSizeAdapter extends cc.Component {
@property baseFontSize: number = 24;
@property(cc.Label) labels: cc.Label[] = [];
onLoad() { this.adaptFontSize(); }
adaptFontSize() { let designSize = cc.view.getDesignResolutionSize(); let frameSize = cc.view.getFrameSize();
let scaleW = frameSize.width / designSize.width; let scaleH = frameSize.height / designSize.height; let scale = Math.min(scaleW, scaleH);
scale = Math.max(0.8, Math.min(scale, 1.5));
let adaptedSize = Math.floor(this.baseFontSize * scale);
this.labels.forEach(label => { label.fontSize = adaptedSize; }); } }
|
常见问题
1. 节点坐标与设计分辨率不匹配
1 2 3 4 5 6 7
| this.node.x = 100;
let designSize = cc.view.getDesignResolutionSize(); this.node.x = designSize.width / 2;
|
2. 刘海屏适配
1 2 3 4 5 6 7 8 9
| let visibleSize = cc.view.getVisibleSize(); let visibleOrigin = cc.view.getVisibleOrigin();
if (visibleOrigin.y > 0) { this.topBar.y -= visibleOrigin.y; }
|
3. Canvas 尺寸与 WinSize 不一致
1 2 3 4 5 6 7 8 9 10
| let canvas = this.getComponent(cc.Canvas); let designRes = canvas.designResolution;
let winSize = cc.winSize;
cc.log("Design:", designRes.width, designRes.height); cc.log("WinSize:", winSize.width, winSize.height);
|
小结
| API |
返回值含义 |
使用建议 |
| getDesignResolutionSize |
设计分辨率 |
UI 布局基准 |
| getFrameSize |
实际屏幕像素 |
设备判断、资源选择 |
| getVisibleSize |
可见区域大小 |
安全区域计算 |
| getWinSize |
设计像素画布 |
最常用的参考尺寸 |
核心原则:只关注设计分辨率和视图大小,让 Cocos Creator 的适配系统处理与设备像素的转换。
这些是我项目中常用的方法,基本能覆盖各种适配场景。