iphoneX 等全面屏手机的适配教程(附代码)看完记得点赞哦!

phoneX和安卓全面屏手机的出现增加了游戏分辨率适配的难度

以横版游戏为例,一般采用16:9的设计分辨率,匹配iphone5~iphone8以及几乎全部安卓手机

对于iphone4/4s(3:2)和ipad(4:3)这种更方的分辨率,只需要使用FIXED_WIDTH模式,再处理顶部和底部多出来的显示区域即可

常用的做法是把背景图做大,顶部底部UI使用widget贴canvas边

然而iphoneX和全面屏手机的分辨率都大于等于2:1,更加狭长的显示区域

继续使用FIXED_WIDTH模式会使界面元素进一步放大

横向看来元素放大与屏幕长度增加成比例,与设计分辨率效果一致

纵向看来元素放大而屏幕高度没变,因此必然会十分拥挤

因此项目中采取了两种新方案处理iphoneX和全面屏的显示

1.判断屏幕高宽比,在大于16:9的设备上切换为SHOW_ALL模式

2.判断屏幕高宽比,在大于16:9的设备上切换为FIXED_HEIGHT模式

下面结合源码谈一下这两种模式的区别

转载注明http://www.cnblogs.com/billyrun/articles/8082415.html
0.常用的几个size比较:


首先需要明确,编辑器/游戏场景中的canvas和页面dom里的canvas实际上不是同一个东西

cc.director.getScene().children[0] == cc.game.canvas :x:结果是false

二者指的都是canvas,但前者场景canvas是引擎中的node节点,后者domCanvas是页面dom节点

场景中的size
首先说最常用的cc.director.getWinSize()

cc.director.getWinSize() == 场景canvas.getContentSize() :white_check_mark:结果是true

这个值是相对设计分辨率而言的相对设计大小

比如设计分辨率1136768的场景,在任何分辨率16:9的浏览器内,这个winsize都是1136768,尽管浏览器窗口实际大小各不相同

换句话说,winsize其实只关心高宽比例,最大限度让游戏内坐标系与设备窗口无关

假设我用ipad全屏进入这个场景,相应的winsize会变成1136*852,因为定宽的情况下canvas纵向撑满了屏幕

dom中的size


其次是cc.game.frame , cc.game.container , cc.game.canvas,这三个属性都是dom节点,对应上图1,2,3

cc.game.frame是整个页面,如图是被嵌套在iframe中的这个游戏子页面

CCView中计算页面大小/是否转屏都是由cc.game.frame.clientHeight/clientWidth获取页面大小

(window.innerWidth/innerHeight其实与之对应是相同的)

cc.view._frameSize/cc.view.getFrameSize()记录的就是这个大小

cc.game.container是包装游戏的容器

转屏时候该容器会改变css强转90度

cc.game.canvas是游戏内容渲染容器

SHOW_ALL模式下可能会比页面窗口小

定宽定高等其余情况,canvas会与页面窗口一样大

1.场景设置与显示模式的对应关系
当勾选场景Canvas定高或定宽时即使用相应显示模式

同时勾选定高和定宽,对应SHOW_ALL模式,显示全部游戏内容,屏幕不足处留黑边

同时不选定高和定宽,对应NO_BORDER模式,无黑边,游戏内容可能会显示不全


CCCanvas.applySettings: function () {
var ResolutionPolicy = cc.ResolutionPolicy;
var policy;

    if (this.fitHeight && this.fitWidth) {
        policy = ResolutionPolicy.SHOW_ALL;
    }
    else if (!this.fitHeight && !this.fitWidth) {
        policy = ResolutionPolicy.NO_BORDER;
    }
    else if (this.fitWidth) {
        policy = ResolutionPolicy.FIXED_WIDTH;
    }
    else {      // fitHeight
        policy = ResolutionPolicy.FIXED_HEIGHT;
    }

    var designRes = this._designResolution;
    if (CC_EDITOR) {
        cc.engine.setDesignResolutionSize(designRes.width, designRes.height);
    }
    else {
        cc.view.setDesignResolutionSize(designRes.width, designRes.height, policy);
    }
}

2.页面适配流程
在游戏初始化的过程中会依次执行以下代码

CCView构造函数中首先初始化浏览器信息,获取页面尺寸(或来自window.innerHeight/innerWidth,不同浏览器有别)

__BrowserGetter.init(this);
然后初始化frameSize,得到"外框"区域大小

_t._initFrameSize();
此后系统会设置设计分辨率

首先设置显示模式setResolutionPolicy

此后是policy.apply(十分重要)

最后会保存winSize(相当于cc.director.setWinSize)并设置visibleRect


setDesignResolutionSize: function (width, height, resolutionPolicy) {
// Defensive code

    this.setResolutionPolicy(resolutionPolicy);
    var policy = this._resolutionPolicy;
    
    ......

    this._originalDesignResolutionSize.width = this._designResolutionSize.width = width;
    this._originalDesignResolutionSize.height = this._designResolutionSize.height = height;

    var result = policy.apply(this, this._designResolutionSize);

    ......

    // reset director's member variables to fit visible rect
    var director = cc.director;
    director._winSizeInPoints.width = this._designResolutionSize.width;
    director._winSizeInPoints.height = this._designResolutionSize.height;
    policy.postApply(this);
    cc.winSize.width = director._winSizeInPoints.width;
    cc.winSize.height = director._winSizeInPoints.height;

    ......

    cc.visibleRect && cc.visibleRect.init(this._visibleRect);
},

setDesignResolutionSize: function (width, height, resolutionPolicy) {
// Defensive code

    this.setResolutionPolicy(resolutionPolicy);
    var policy = this._resolutionPolicy;
    
    ......

    this._originalDesignResolutionSize.width = this._designResolutionSize.width = width;
    this._originalDesignResolutionSize.height = this._designResolutionSize.height = height;

    var result = policy.apply(this, this._designResolutionSize);

    ......

    // reset director's member variables to fit visible rect
    var director = cc.director;
    director._winSizeInPoints.width = this._designResolutionSize.width;
    director._winSizeInPoints.height = this._designResolutionSize.height;
    policy.postApply(this);
    cc.winSize.width = director._winSizeInPoints.width;
    cc.winSize.height = director._winSizeInPoints.height;

    ......

    cc.visibleRect && cc.visibleRect.init(this._visibleRect);
},

对于不同适配策略

policy.apply使用不同逻辑

cc.ResolutionPolicy构造函数接收两个参数分别对应_containerStrategy与_contentStrategy

_t._rpExactFit = new cc.ResolutionPolicy(_strategyer.EQUAL_TO_FRAME, _strategy.EXACT_FIT);
_t._rpShowAll = new cc.ResolutionPolicy(_strategyer.PROPORTION_TO_FRAME, _strategy.SHOW_ALL);
_t._rpNoBorder = new cc.ResolutionPolicy(_strategyer.EQUAL_TO_FRAME, _strategy.NO_BORDER);
_t._rpFixedHeight = new cc.ResolutionPolicy(_strategyer.EQUAL_TO_FRAME, _strategy.FIXED_HEIGHT);
_t._rpFixedWidth = new cc.ResolutionPolicy(_strategyer.EQUAL_TO_FRAME, _strategy.FIXED_WIDTH);
这里首先关心的是_containerStrategy

可以发现只有showAll对应PROPORTION_TO_FRAME

其余的模式都对应EQUAL_TO_FRAME

接下来具体来看两者的区别


var EqualToFrame = cc.ContainerStrategy.extend({
apply: function (view) {
var frameH = view._frameSize.height, containerStyle = cc.container.style;
this._setupContainer(view, view._frameSize.width, view._frameSize.height);
// Setup container’s margin and padding
if (view._isRotated) {
containerStyle.margin = '0 0 0 ’ + frameH + ‘px’;
}
else {
containerStyle.margin = ‘0px’;
}
containerStyle.padding = “0px”;
}
});

/**
 * @class ProportionalToFrame
 * @extends ContainerStrategy
 */
var ProportionalToFrame = cc.ContainerStrategy.extend({
    apply: function (view, designedResolution) {
        var frameW = view._frameSize.width, frameH = view._frameSize.height, containerStyle = cc.container.style,
            designW = designedResolution.width, designH = designedResolution.height,
            scaleX = frameW / designW, scaleY = frameH / designH,
            containerW, containerH;

        scaleX < scaleY ? (containerW = frameW, containerH = designH * scaleX) : (containerW = designW * scaleY, containerH = frameH);

        // Adjust container size with integer value
        var offx = Math.round((frameW - containerW) / 2);
        var offy = Math.round((frameH - containerH) / 2);
        containerW = frameW - 2 * offx;
        containerH = frameH - 2 * offy;

        this._setupContainer(view, containerW, containerH);
        if (!CC_EDITOR) {
            // Setup container's margin and padding
            if (view._isRotated) {
                containerStyle.margin = '0 0 0 ' + frameH + 'px';
            }
            else {
                containerStyle.margin = '0px';
            }
            containerStyle.paddingLeft = offx + "px";
            containerStyle.paddingRight = offx + "px";
            containerStyle.paddingTop = offy + "px";
            containerStyle.paddingBottom = offy + "px";
        }
    }
});

var EqualToFrame = cc.ContainerStrategy.extend({
apply: function (view) {
var frameH = view._frameSize.height, containerStyle = cc.container.style;
this._setupContainer(view, view._frameSize.width, view._frameSize.height);
// Setup container’s margin and padding
if (view._isRotated) {
containerStyle.margin = '0 0 0 ’ + frameH + ‘px’;
}
else {
containerStyle.margin = ‘0px’;
}
containerStyle.padding = “0px”;
}
});

/**
 * @class ProportionalToFrame
 * @extends ContainerStrategy
 */
var ProportionalToFrame = cc.ContainerStrategy.extend({
    apply: function (view, designedResolution) {
        var frameW = view._frameSize.width, frameH = view._frameSize.height, containerStyle = cc.container.style,
            designW = designedResolution.width, designH = designedResolution.height,
            scaleX = frameW / designW, scaleY = frameH / designH,
            containerW, containerH;

        scaleX < scaleY ? (containerW = frameW, containerH = designH * scaleX) : (containerW = designW * scaleY, containerH = frameH);

        // Adjust container size with integer value
        var offx = Math.round((frameW - containerW) / 2);
        var offy = Math.round((frameH - containerH) / 2);
        containerW = frameW - 2 * offx;
        containerH = frameH - 2 * offy;

        this._setupContainer(view, containerW, containerH);
        if (!CC_EDITOR) {
            // Setup container's margin and padding
            if (view._isRotated) {
                containerStyle.margin = '0 0 0 ' + frameH + 'px';
            }
            else {
                containerStyle.margin = '0px';
            }
            containerStyle.paddingLeft = offx + "px";
            containerStyle.paddingRight = offx + "px";
            containerStyle.paddingTop = offy + "px";
            containerStyle.paddingBottom = offy + "px";
        }
    }
});

_setupContainer: function (view, w, h) {
var locCanvas = cc.game.canvas, locContainer = cc.game.container;

    if (cc.sys.platform !== cc.sys.WECHAT_GAME) {
        if (cc.sys.os === cc.sys.OS_ANDROID) {
            document.body.style.width = (view._isRotated ? h : w) + 'px';
            document.body.style.height = (view._isRotated ? w : h) + 'px';
        }
        // Setup style
        locContainer.style.width = locCanvas.style.width = w + 'px';
        locContainer.style.height = locCanvas.style.height = h + 'px';
    }
    // Setup pixel ratio for retina display
    var devicePixelRatio = view._devicePixelRatio = 1;
    if (view.isRetinaEnabled())
        devicePixelRatio = view._devicePixelRatio = Math.min(2, window.devicePixelRatio || 1);
    // Setup canvas
    locCanvas.width = w * devicePixelRatio;
    locCanvas.height = h * devicePixelRatio;
    cc._renderContext.resetCache && cc._renderContext.resetCache();
},

EqualToFrame中直接使用frameSize设置container

this._setupContainer(view, view._frameSize.width, view._frameSize.height);

ProportionalToFrame经过计算得到缩小后的size设置container

在_setupContainer中设置了game.container和game.canvas的dom元素size

这里把游戏逻辑与H5页面元素结合到了一起

再回顾上文所述可以发现

只有SHOW_ALL一种模式下canvas不一定铺满屏幕(宽高比不一定同frame),其余模式canvas均铺满了屏幕(宽高比同frame)

而我们常用的WinSize始终等于场景的CanvasSize

3.总结
因此,回到最初的两种解决方案

1.判断屏幕高宽比,在大于16:9的设备上切换为SHOW_ALL模式

游戏内容16:9居中显示,两侧留黑边

游戏内坐标完全同设计分辨率

黑边区域并不在游戏渲染范围以内

想要装饰黑边需要使用dom元素

2.判断屏幕高宽比,在大于16:9的设备上切换为FIXED_HEIGHT模式

游戏内容16:9居中显示(widget组件靠边的除外,按frame靠边)

若无widget左右贴边且背景图刚好是设计分辨率大小,两侧会留黑边

黑边区域也包含在渲染区域内,如果游戏内背景图够大可以显示

canvas变长变扁,坐标系会发生左右偏移

显然方案1比较简单

需要特别指出的时,按分辨率切换渲染模式时不能使用16:9作为阈值

因为1136/640!=16/9,只是近似相等!

2赞

你要是能写简单点。。。。。告诉我们改什么地方,如何操作能实现效果就好了~~~

1赞

这么复杂的么?

你好 我这边挂在container上的元素 转屏时在IOS上不显示不出来 安卓正常 请问下怎么解决的