CocosCreator分辨率適配--全面屏/IphoneX


iphoneX和安卓全面屏手機的出現增加了游戲分辨率適配的難度

以橫版游戲為例,一般采用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 ❌結果是false

二者指的都是canvas,但前者場景canvas是引擎中的node節點,后者domCanvas是頁面dom節點

場景中的size

首先說最常用的cc.director.getWinSize()

cc.director.getWinSize() == 場景canvas.getContentSize() ✅結果是true

這個值是相對設計分辨率而言的相對設計大小

比如設計分辨率1136*768的場景,在任何分辨率16:9的瀏覽器內,這個winsize都是1136*768,盡管瀏覽器窗口實際大小各不相同

換句話說,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);
    },    
setResolutionPolicy: function (resolutionPolicy) {
        var _t = this;
        if (resolutionPolicy instanceof cc.ResolutionPolicy) {
            _t._resolutionPolicy = resolutionPolicy;
        }
        // Ensure compatibility with JSB
        else {
            var _locPolicy = cc.ResolutionPolicy;
            if(resolutionPolicy === _locPolicy.EXACT_FIT)
                _t._resolutionPolicy = _t._rpExactFit;
            if(resolutionPolicy === _locPolicy.SHOW_ALL)
                _t._resolutionPolicy = _t._rpShowAll;
            if(resolutionPolicy === _locPolicy.NO_BORDER)
                _t._resolutionPolicy = _t._rpNoBorder;
            if(resolutionPolicy === _locPolicy.FIXED_HEIGHT)
                _t._resolutionPolicy = _t._rpFixedHeight;
            if(resolutionPolicy === _locPolicy.FIXED_WIDTH)
                _t._resolutionPolicy = _t._rpFixedWidth;
        }
    },

對於不同適配策略

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";
            }
        }
    });
_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,只是近似相等!

 

ps.

在使用dom元素裝飾黑邊時,safari瀏覽器在移動設備轉屏時與chrome會有較大差異

以下圖為例

 當設備翻轉時,若不改變設置邊框css,很可能出現這種情況

而safari瀏覽器中,右側未能顯示在屏幕中的邊框,也會計算頁面大小

即改變

window.innerWidth/innerHeight

cc.game.frame.clientWidth/clientHeight

(chrome瀏覽器貌似不會變!)

這個值的改變直接導致w/h是否大於1的改變,因此會使引擎cc.view._isRotated計算錯誤!

對於橫版游戲

原本橫屏時引擎設置cc.game.container.style.transform = 'rotate(0deg)'

豎屏時cc.game.container.style.transform = 'rotate(90deg)' , 即通過css強行將游戲轉向為橫屏

當以上cc.view._isRotated計算錯誤時可想而知 游戲的轉向也會出錯

 

解決方案(從簡便到徹底)

1.監聽orientationchange事件,在豎屏時不顯示邊框(display:none) , 這樣邊框即使位置不對也不會改變頁面大小

2.將邊框加入cc.game.container而不是加在body上,讓邊框跟着一起轉

3.徹底棄用SHOW_ALL+邊框的適配方式,改做定寬轉定高,在定高顯示方案中選用更寬的底圖,並手動處理一些橫向坐標偏移


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM