Phaser的屏幕适配问题是群里讨论得最多的问题,也是很关键的一个问题。
基本看过我的视频的同学,或者稍微用过Phaser的同学都应该知道了,Phaser中的ScaleManager是专门用来解决Phaser屏幕适配问题的。如果不知道ScaleManager是什么的同学,建议去看看我的视频。
在了解了ScaleManager之后,我们还会有两个问题。第一、ScaleManager有没有办法做到不变形,有能充满整个屏幕?第二、怎么让浏览器横屏?
首先,我们来说第一个问题,这其实是一个数学问题。这个问题的本质是,两个宽高比不想等的矩形,有没有办法通过等比缩放让它们完全重叠?相信任何一个小学数学学得还不错的人都能给出答案,不可能。
但是有没有办法解决这个问题呢?当然有,那就是动态设置游戏区域,拿到屏幕大小之后再等比缩放成一个游戏区域大小不就行了。但是这会带来一个问题,那就是你的元素无法按照坐标来定位了,因为你不知道游戏的宽高是多少,它和屏幕尺寸有关。这在大多数情况下会让你很难受,但是在一种情况下不会,那就是你的世界大小比你的游戏区域大,这样,你可以固定世界大小,然后元素按照世界大小来定位。
当然,我们还有别的方法,那就是在html中做一些背景,可以让游戏融入到背景中,而不是出现两边的黑边或者白边。具体使用什么方法,就看大家自己的实际情况了。
接下来说说本文重头戏:横屏适配。
很多人问我,html能不能让浏览器强制横屏,phaser中设置了横屏为什么没有用?答案肯定是不能,也没有用。其实道理很简单,因为浏览器是html的环境,html是没有权利去改变它外在的环境的,至少现在不能。
所以我们会看到很多游戏,打开之后,它建议你横屏玩耍。但是这样其实代价很大,因为很多人并不知道怎么横屏。其实我想要的是这样的效果:

也就是虽然是竖屏,但是游戏以横屏的样子展示,这样,再傻的用户都知道要把手机横过来玩吧?那么当用户横过手机的时候,有两种情况,第一,用户的手机设置了竖屏锁定,即使横过来,其实还是竖屏,这种情况没问题。第二,用户没有设置竖屏锁定,横过来之后,系统切换成横屏,那么我们这个旋转90度的屏幕正好又错位了?答案当然不是。

当屏幕横过来的时候,我们就不旋转90度,游戏依然完美呈现。下面来说说怎么实现的。
这个实现涉及到Phaser底层源码,还有PIXI的一些机制,如果大家看不懂,可以跳过,直接看最后怎么用就行了。
首先,我们要考虑,旋转90度应该怎么做,其实只要把世界旋转90度,就可以了,但是世界旋转了之后,它是按照那个点进行旋转的,旋转之后是否要进行坐标调整?看代码。
Phaser.World.prototype.displayObjectUpdateTransform = function() {
if(!game.scale.correct) {
this.x = game.camera.y + game.width;
this.y = -game.camera.x;
this.rotation = Phaser.Math.degToRad(Phaser.Math.wrapAngle(90));
} else {
this.x = -game.camera.x;
this.y = -game.camera.y;
this.rotation = 0;
}
PIXI.DisplayObject.prototype.updateTransform.call(this);
}注意,这里调整坐标的时候,加入了camera的修正,这是因为在世界大小比游戏区域大小大的时候,camera就不一定在(0,0)点了,所以要考虑进去。
加了这个当然不够,还需要在游戏中加入:
game.scale.onOrientationChange.add(function() {
if(game.scale.isLandscape) {
game.scale.correct = true;
game.scale.setGameSize(WIDTH, HEIGHT);
} else {
game.scale.correct = false;
game.scale.setGameSize(HEIGHT, WIDTH);
}
}, this)每一次横竖屏变化的时候,我们需要实时切换游戏的宽高,道理也是显而易见的。横屏的时候,你的游戏是1920x1080,竖屏的时候,你的游戏就应该是1080x1920,这样才能正好匹配屏幕。
怎么使用也很简单,在游戏的BootState里面加入这些代码就行,其余的事情和之前的做法一样,只是有一点提醒大家,你的game.width和game.height是多少呢?已经不一定了,所以在元素定位的时候不要用它们去定位。我在tacit中的做法是,全局定义好游戏宽高,后面所有定位按照它来做,就不会有问题了。
好了,横屏适配就说到这里,最后给出代码地址:https://github.com/channingbreeze/games/blob/master/tacit/js/states/BootState.js
-------------------------------------------------------------------------
2017年11月29日补充
这篇文章发出来后,QQ群里就有反馈,竖屏的时候,拖动精灵,运动方向和实际方向是垂直的,试了一下,还真是,翻了翻源码,暂时没有找到特别好的解决方案,针对2.6.2版本打一个补丁。
把这段代码贴上就行了
/**
* Called by Pointer when drag starts on this Sprite. Should not usually be called directly.
*
* @method Phaser.InputHandler#startDrag
* @param {Phaser.Pointer} pointer
*/
Phaser.InputHandler.prototype.startDrag = function (pointer) {
var x = this.sprite.x;
var y = this.sprite.y;
this.isDragged = true;
this._draggedPointerID = pointer.id;
this._pointerData[pointer.id].camX = this.game.camera.x;
this._pointerData[pointer.id].camY = this.game.camera.y;
this._pointerData[pointer.id].isDragged = true;
if (this.sprite.fixedToCamera) {
if (this.dragFromCenter) {
var bounds = this.sprite.getBounds();
this.sprite.cameraOffset.x = this.globalToLocalX(pointer.x) + (this.sprite.cameraOffset.x - bounds.centerX);
this.sprite.cameraOffset.y = this.globalToLocalY(pointer.y) + (this.sprite.cameraOffset.y - bounds.centerY);
}
this._dragPoint.setTo(this.sprite.cameraOffset.x - pointer.x, this.sprite.cameraOffset.y - pointer.y);
} else {
if (this.dragFromCenter) {
var bounds = this.sprite.getBounds();
this.sprite.x = this.globalToLocalX(pointer.x) + (this.sprite.x - bounds.centerX);
this.sprite.y = this.globalToLocalY(pointer.y) + (this.sprite.y - bounds.centerY);
}
if(game.scale.correct) {
this._dragPoint.setTo(this.sprite.x - this.globalToLocalX(pointer.x), this.sprite.y - this.globalToLocalY(pointer.y));
} else {
this._dragPoint.setTo(-this.sprite.y - this.globalToLocalX(pointer.x), this.sprite.x - this.globalToLocalY(pointer.y));
}
}
this.updateDrag(pointer, true);
if (this.bringToTop) {
this._dragPhase = true;
this.sprite.bringToTop();
}
this.dragStartPoint.set(x, y);
this.sprite.events.onDragStart$dispatch(this.sprite, pointer, x, y);
this._pendingDrag = false;
};
/**
* Called as a Pointer actively drags this Game Object.
*
* @method Phaser.InputHandler#updateDrag
* @private
* @param {Phaser.Pointer} pointer - The Pointer causing the drag update.
* @param {boolean} fromStart - True if this is the first update, immediately after the drag has started.
* @return {boolean}
*/
Phaser.InputHandler.prototype.updateDrag = function (pointer, fromStart) {
if (fromStart === undefined) { fromStart = false; }
if (pointer.isUp) {
this.stopDrag(pointer);
return false;
}
var px = this.globalToLocalX(pointer.x) + this._dragPoint.x + this.dragOffset.x;
var py = this.globalToLocalY(pointer.y) + this._dragPoint.y + this.dragOffset.y;
if (this.sprite.fixedToCamera) {
if (this.allowHorizontalDrag) {
this.sprite.cameraOffset.x = px;
}
if (this.allowVerticalDrag) {
this.sprite.cameraOffset.y = py;
}
if (this.boundsRect) {
this.checkBoundsRect();
}
if (this.boundsSprite) {
this.checkBoundsSprite();
}
if (this.snapOnDrag) {
this.sprite.cameraOffset.x = Math.round((this.sprite.cameraOffset.x - (this.snapOffsetX % this.snapX)) / this.snapX) * this.snapX + (this.snapOffsetX % this.snapX);
this.sprite.cameraOffset.y = Math.round((this.sprite.cameraOffset.y - (this.snapOffsetY % this.snapY)) / this.snapY) * this.snapY + (this.snapOffsetY % this.snapY);
this.snapPoint.set(this.sprite.cameraOffset.x, this.sprite.cameraOffset.y);
}
} else {
var cx = this.game.camera.x - this._pointerData[pointer.id].camX;
var cy = this.game.camera.y - this._pointerData[pointer.id].camY;
if (this.allowHorizontalDrag) {
if(game.scale.correct) {
this.sprite.x = px + cx;
} else {
this.sprite.x = py + cy;
}
}
if (this.allowVerticalDrag) {
if(game.scale.correct) {
this.sprite.y = py + cy;
} else {
this.sprite.y = -(px + cx);
}
}
if (this.boundsRect) {
this.checkBoundsRect();
}
if (this.boundsSprite) {
this.checkBoundsSprite();
}
if (this.snapOnDrag) {
this.sprite.x = Math.round((this.sprite.x - (this.snapOffsetX % this.snapX)) / this.snapX) * this.snapX + (this.snapOffsetX % this.snapX);
this.sprite.y = Math.round((this.sprite.y - (this.snapOffsetY % this.snapY)) / this.snapY) * this.snapY + (this.snapOffsetY % this.snapY);
this.snapPoint.set(this.sprite.x, this.sprite.y);
}
}
this.sprite.events.onDragUpdate.dispatch(this.sprite, pointer, px, py, this.snapPoint, fromStart);
return true;
}示例用法见:https://github.com/channingbreeze/games/blob/master/crazybird/js/states/BootState.js
还有一个问题,使用camera去follow一个精灵的时候,也可能出现不对的情况,可以自己在update中实现一个follow就行,代码也很简单,自己调整一下camera的x和y:
CrazyBird.Bird.prototype.ownFollow = function() {
game.camera.x = this.x - WIDTH/2;
game.camera.y = this.y;
if(game.camera.x > game.world.width - WIDTH) {
game.camera.x = game.world.width - WIDTH;
}
}示例用法见:https://github.com/channingbreeze/games/blob/master/crazybird/js/objects/Bird.js