精华 Phaser横竖屏适配
发布于 7 年前 作者 liangdas 6672 次浏览 来自 分享

Phaser横竖屏适配

目前H5游戏开发基本都会优先考虑移动端,因此也就涉及到移动端横竖屏兼容的问题。

这边文章主要分享我再Phaser开发过程中所遇到的屏幕适配问题,以及解决方案

横竖屏的坑

不同种类的游戏对屏幕适配的要求不同,竖屏的游戏直接强制竖屏就可以了,而如果是横屏游戏的话就比较麻烦了,移动端不同的手机,不同的浏览器对H5的兼容性不一样,造成部分情况下屏幕是无法横屏的

  1. 用户界面强制竖屏锁定了(这种情况很常见)
  2. 浏览器无法设置横屏(这种情况比较少,但也遇到过)

横竖屏的解决方案

我所采用的解决思路是这样的

  1. 默认游戏宽带>大于高度(横屏游戏)
  2. 保证canvas屏幕居中
  3. 判断当前手机横竖屏状态
  4. 如果是竖屏则旋转canvas 90读,这时在用户眼中游戏就变成横屏的了
  5. 如果当前手机是横屏状态则不处理

这个解决思路实现过程中会遇到几个问题

  1. 旋转canvas后需要调整一下canvas与html的中心点对齐
  2. 旋转canvas后玩家点击事件也需要重新计算,因为对引擎来说当前依然是竖屏状态

以上的这种解决方案目前我已经在但部分的常用机型上做过测试,基本都没有问题

代码

  1. phaser容器在html撑满屏幕

    <style>
        html{
            overflow:hidden;
        }
        body {
            text-align:center;
            margin:0 auto;
            padding: 0;
            width: 100%;
            height:100%;
            scrolling:"no";
            overflow-x:hidden;
            overflow-y:hidden;
        }
        .main{
            z-index: 10;
            position: absolute;
            display: none;
            height: 100%;
            width: 100%;
        }
        .game {
            z-index: 0;
            margin: auto;
            display:none;
            position: absolute;
            top: 0; left: 0; bottom: 0; right: 0;
            background-color:lightcoral;
        }
    </style>
    <body>
    	<div id="game" class="game" >
    	
    	</div>
    </body>
    
  2. phaser屏幕横竖屏切换的封装类代码

    使用无需了解这些代码内部的具体含义,在后面会给出简单的使用方法

inherits.js
	'use strict';
	/**
	 * Created by liangdas on 2016/12/6 0006.
	 * Email :1587790525@qq.com
	 * 这是一个 javascript 类继承函数,
	 * 与以往的继承函数不同的是这个函数,
	 * 可以直接从一个已存在的普通构造函数继承。
	 */
	function object(o){
	    function W(){
	    }
	    W.prototype=o;
	    return new W();
	}
	function inheritPrototype(SubType,SuperType){
	    var prototype;
	    if(typeof Object.create==='function'){
	        prototype=Object.create(SuperType.prototype);
	    }else{
	        prototype=object.create(SuperType.prototype);
	    }
	    prototype.constructor=SubType;
	    SubType.prototype=prototype;
	}
	module.exports = function (superCtor, prop) {
	    return function () {
	        var fnTest = /xyz/.test(function () {
	            xyz;
	        }) ? /\b_super\b/ : /.*/;
	
	        var _super = superCtor.prototype;
	        //// The base Class implementation (does nothing)
	        function baseClass() {
	            if (typeof baseClass.prototype.ctor==="undefined") {
	            }else{
	                var args = new Array()
	                for(var k in arguments){
	                    args.push(arguments[k]);
	                }
	                baseClass.prototype.ctor.apply(this, args);
	            }
	        };
	        // 空函数F:
	        //var F = function F() {};
	        //// 把F的原型指向Student.prototype:
	        //F.prototype = superCtor.prototype;
	        ////F.prototype = Object.create(superCtor.prototype);
	        //// 把PrimaryStudent的原型指向一个新的F对象,F对象的原型正好指向Student.prototype:
	        //baseClass.prototype = new F();
	        //// 把PrimaryStudent原型的构造函数修复为PrimaryStudent:
	        //baseClass.prototype.constructor = baseClass;
	        inheritPrototype(baseClass,superCtor);
	        var prototype = baseClass.prototype;
	        if(typeof (_super)==="undefined"){
	
	        }else{
	            //_super["ctor"]="ss";
	            if (typeof (_super["ctor"])==="undefined") {
	                _super["ctor"] = superCtor;
	            }
	        }
	
	        // Copy the properties over onto the new prototype
	        for (var name in prop) {
	            // Check if we're overwriting an existing function
	            prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? function (name, fn) {
	                return function () {
	                    //var tmp_superclass = this.superclass;
	                    var tmp = this._super;
	                    // Add a new ._super() method that is the same method
	                    // but on the super-class
	                    this._super = _super[name];
	                    //this.superclass=superCtor.bind(this);
	                    // The method only need to be bound temporarily, so we
	                    // remove it when we're done executing
	                    var args = new Array()
	                    for(var k in arguments){
	                        args.push(arguments[k]);
	                    }
	                    var ret = fn.apply(this,args);
	                    this._super = tmp;
	                    //this.superclass=tmp_superclass;
	                    return ret;
	                };
	            }(name, prop[name]) : prop[name];
	        }
	        return baseClass;
	    }();
	};
MyScaleManager.js
"use strict";
var extend = require('./inherits.js');
/**
 * Created by liangdas on 16/12/15.
 * Email 1587790525@qq.com
 */
module.exports = extend(function () {}, {
    ctor: function ctor(gameDiv,Phaser) {
        if (typeof Phaser.DOM.hadhook === 'function' && Phaser.DOM.hadhook()){
            return
        }
        this.Phaser=Phaser;
        this.gameDiv = gameDiv;
        this.proDOM = Phaser.DOM;
        this.isMyLandscapeMode = false;
        if (this.proDOM.getScreenOrientation() != "landscape-primary") {
            //如果当前是竖屏 启动自定义横屏
            this.setMyLandscapeMode(true, true);
        } else {
            this.refresh();
        }
        var BaseDOM = extend(function () {}, Phaser.DOM);

        var MyDOM = extend(BaseDOM, {
            getScreenOrientation: function getScreenOrientation() {
                var orientation = this._super.apply(this, arguments);

                if (document.documentElement.clientWidth !== this.proDocumentWidth || document.documentElement.clientHeight !== this.proDocumentHeight) {
                    Phaser.myScaleManager.refresh(); //刷新界面宽高 非常有用

                    this.proDocumentWidth = document.documentElement.clientWidth;
                    this.proDocumentHeight = document.documentElement.clientHeight;
                }

                if (orientation != "landscape-primary") {
                    //当前是竖屏
                    if (!Phaser.myScaleManager.isMyLandscape()) {
                        //未启动 自定义横屏
                        Phaser.myScaleManager.setMyLandscapeMode(true, true);
                    }
                    return "landscape-primary";
                } else {
                    //切换到横屏模式
                    if (Phaser.myScaleManager.isMyLandscape()) {
                        //关闭自定义横屏模式
                        Phaser.myScaleManager.setMyLandscapeMode(false, true);
                    }
                    return orientation;
                }
            },
            getOffset: function getOffset() {
                var rel = this._super.apply(this, arguments);
                //console.log(rel);
                return rel;
            },
            getBounds: function getBounds() {
                var rel = this._super.apply(this, arguments);
                //console.log(rel);
                return rel;
            },
            calibrate: function calibrate() {
                var rel = this._super.apply(this, arguments);
                //console.log(rel);
                return rel;
            },
            getAspectRatio: function getAspectRatio() {
                var rel = this._super.apply(this, arguments);
                //console.log(rel);
                return rel;
            },
            inLayoutViewport: function inLayoutViewport() {
                var rel = this._super.apply(this, arguments);
                //console.log(rel);
                return rel;
            },
            hadhook:function(){
                return true
            }
        });
        Phaser.DOM = new MyDOM();

        var _startPointer = Phaser.Input.prototype.startPointer;
        Phaser.Input.prototype.startPointer = function (event) {
            return _startPointer.call(this, this.copyEvent(event));
        };
        var _updatePointer = Phaser.Input.prototype.updatePointer;
        Phaser.Input.prototype.updatePointer = function (event) {
            return _updatePointer.call(this, this.copyEvent(event));
        };
        var _stopPointer = Phaser.Input.prototype.stopPointer;
        Phaser.Input.prototype.stopPointer = function (event) {
            return _stopPointer.call(this, this.copyEvent(event));
        };
        Phaser.Input.prototype.copyEvent = function (event) {
            if (!Phaser.myScaleManager.isMyLandscape()) {
                //未启动 自定义横屏
                return event;
            }
            var target = event.target;
            var myevent = this.extendCopy(event);

            var _cx = myevent.clientX;
            var _cy = myevent.clientY;
            var _px = myevent.pageX;
            var _py = myevent.pageY;
            var _sx = myevent.screenX;
            var _sy = myevent.screenY;
            myevent.clientX = _cy;
            myevent.clientY = target.clientHeight - _cx;
            myevent.pageX = _py;
            myevent.pageY = target.clientHeight - _px;
            //myevent.rotationAngle=Math.PI/2;
            return myevent;
        };
        Phaser.Input.prototype.extendCopy = function (p) {
            var c = {};
            for (var i in p) {
                c[i] = p[i];
            }
            c.uber = p;
            return c;
        };
        var _getParentBounds = Phaser.ScaleManager.prototype.getParentBounds;
        Phaser.ScaleManager.prototype.getParentBounds = function () {
            var rel = _getParentBounds.apply(this, arguments);
            var _width = rel.width;
            var _height = rel.height;
            if (Phaser.myScaleManager.isMyLandscape()) {
                rel.width = _height;
                rel.height = _width;
            }
            return rel;
        };
    },
    boot: function boot(game) {
        this.game = game;
    },
    refresh: function () {
        document.body.style.width = document.documentElement.clientWidth + "px";
        document.body.style.height = document.documentElement.clientHeight + "px";

        if (document.documentElement.clientHeight >= document.documentElement.clientWidth) {
            //竖屏
            this.gameDiv.style.height = document.body.clientWidth + "px";
            this.gameDiv.style.width = document.body.clientHeight + "px";
            this.gameDiv.style.transform = "rotate(90deg)";
            this.gameDiv.style.left = -(document.documentElement.clientHeight - document.documentElement.clientWidth) / 2 + "px";
        } else {
            //横屏
            this.gameDiv.style.width = document.body.clientWidth + "px";
            this.gameDiv.style.height = document.body.clientHeight + "px";
            this.gameDiv.style.transform = "";
            this.gameDiv.style.left = "";
        }
    },
    setMyLandscapeMode: function setMyLandscapeMode(setTo, refresh) {
        refresh = refresh || false;
        this.isMyLandscapeMode = setTo;
        if (refresh) {
            this.refresh();
        }
    },
    isMyLandscape: function isMyLandscape() {
        return this.isMyLandscapeMode;
    }

});

用法

在phaser初始化以后调用以下代码即可

var game = new Phaser.Game(480, 320, Phaser.AUTO, gameDiv);
Phaser.myScaleManager = new MyScaleManager(gameDiv,Phaser);
Phaser.myScaleManager.boot(); //hook phaser环境,可以实现横竖屏自动切换了
3 回复

这个实现方案有点复杂,简版方案可以参考这篇文章:https://www.phaser-china.com/tutorial-detail-16.html

方法挺好赞一个,不过建议在顶上加上array.from的实现(方法来自es6, uc, qq, vivo等手机浏览器均未实现此函数,参考网址如下) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from

// Production steps of ECMA-262, Edition 6, 22.1.2.1 if (!Array.from) { Array.from = (function () { var toStr = Object.prototype.toString; var isCallable = function (fn) { return typeof fn === ‘function’ || toStr.call(fn) === ‘[object Function]’; }; var toInteger = function (value) { var number = Number(value); if (isNaN(number)) { return 0; } if (number === 0 || !isFinite(number)) { return number; } return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); }; var maxSafeInteger = Math.pow(2, 53) - 1; var toLength = function (value) { var len = toInteger(value); return Math.min(Math.max(len, 0), maxSafeInteger); };

// The length property of the from method is 1.
return function from(arrayLike/*, mapFn, thisArg */) {
  // 1. Let C be the this value.
  var C = this;

  // 2. Let items be ToObject(arrayLike).
  var items = Object(arrayLike);

  // 3. ReturnIfAbrupt(items).
  if (arrayLike == null) {
    throw new TypeError('Array.from requires an array-like object - not null or undefined');
  }

  // 4. If mapfn is undefined, then let mapping be false.
  var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
  var T;
  if (typeof mapFn !== 'undefined') {
    // 5. else
    // 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
    if (!isCallable(mapFn)) {
      throw new TypeError('Array.from: when provided, the second argument must be a function');
    }

    // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
    if (arguments.length > 2) {
      T = arguments[2];
    }
  }

  // 10. Let lenValue be Get(items, "length").
  // 11. Let len be ToLength(lenValue).
  var len = toLength(items.length);

  // 13. If IsConstructor(C) is true, then
  // 13. a. Let A be the result of calling the [[Construct]] internal method 
  // of C with an argument list containing the single item len.
  // 14. a. Else, Let A be ArrayCreate(len).
  var A = isCallable(C) ? Object(new C(len)) : new Array(len);

  // 16. Let k be 0.
  var k = 0;
  // 17. Repeat, while k < len… (also steps a - h)
  var kValue;
  while (k < len) {
    kValue = items[k];
    if (mapFn) {
      A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
    } else {
      A[k] = kValue;
    }
    k += 1;
  }
  // 18. Let putStatus be Put(A, "length", len, true).
  A.length = len;
  // 20. Return A.
  return A;
};

}()); }

回到顶部