Phaser横竖屏适配
目前H5游戏开发基本都会优先考虑移动端,因此也就涉及到移动端横竖屏兼容的问题。
这边文章主要分享我再Phaser开发过程中所遇到的屏幕适配问题,以及解决方案
横竖屏的坑
不同种类的游戏对屏幕适配的要求不同,竖屏的游戏直接强制竖屏就可以了,而如果是横屏游戏的话就比较麻烦了,移动端不同的手机,不同的浏览器对H5的兼容性不一样,造成部分情况下屏幕是无法横屏的
- 用户界面强制竖屏锁定了(这种情况很常见)
- 浏览器无法设置横屏(这种情况比较少,但也遇到过)
横竖屏的解决方案
我所采用的解决思路是这样的
- 默认游戏宽带>大于高度(横屏游戏)
- 保证canvas屏幕居中
- 判断当前手机横竖屏状态
- 如果是竖屏则旋转canvas 90读,这时在用户眼中游戏就变成横屏的了
- 如果当前手机是横屏状态则不处理
这个解决思路实现过程中会遇到几个问题
- 旋转canvas后需要调整一下canvas与html的中心点对齐
- 旋转canvas后玩家点击事件也需要重新计算,因为对引擎来说当前依然是竖屏状态
以上的这种解决方案目前我已经在但部分的常用机型上做过测试,基本都没有问题
代码
-
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>
-
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环境,可以实现横竖屏自动切换了
这个实现方案有点复杂,简版方案可以参考这篇文章: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;
};
}()); }