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