2015年2月6日
歡迎!
在連續幾周讨論了平台跳躍遊戲的機制之後,我們也應該休息一下了。是以,本周的教程将基于論壇上經常談論的特性:網格運動,或者可以說得更加明确一點:如何像Pacman(譯者注:在紅白機上叫做吃豆子,或者小精靈)那樣在網格中移動。
我們本周讨論的代碼可以讓玩家優雅地在瓦片地圖中移動,在很小的空間中轉彎。我們還将建構Pacman遊戲的核心代碼。
擷取源代碼
在這裡我隻會着重高亮最重要的部分代碼。是以請先浏覽一下代碼。如果你對一些代碼有疑問,你可以去論壇詢問。
運作/編輯汽車遊戲代碼可以到 jsbin 或者 codepen (譯者:國内使用者可通路runjs)
運作/編輯Pacman代碼可以到jsbin or codepen (譯者:國内使用者可通路runjs)
克隆Phaser程式設計技巧代碼可以到git repo。
基礎設施的建構
我們需要一個玩家精靈和一張瓦片地圖。瓦片地圖包含來了關卡的布局。在這裡,我們在Tiled(譯者注:Tiled官網 http://www.mapeditor.org/)中畫了一個簡單的關卡:
這個軟體導出了一個JSON檔案,然後和tileset一起導入遊戲中。在 create 方法中我們生成了所有的對象:
this.map = this.add.tilemap('map');
this.map.addTilesetImage('tiles', 'tiles');
this.layer = this.map.createLayer('Tile Layer 1');
this.map.setCollision(20, true, this.layer);
this.car = this.add.sprite(48, 48, 'car');
this.car.anchor.set(0.5);
this.physics.arcade.enable(this.car);
this.cursors = this.input.keyboard.createCursorKeys();
this.move(Phaser.DOWN);
我們建立了地圖和圖層。碰撞被設定到20号瓦片上(暗灰色的磚塊)。汽車擺放在地圖的左上角,并且一開始就向下移動。.
在 update 函數中,我們檢測與瓦片地圖的碰撞:
this.physics.arcade.collide(this.car, this.layer);
目前的問題
玩家的地圖中的移動是一件需要解決的遊戲的挑戰。 盡管表面上這個看起來很簡單,但是實際上這需要玩家對象對周邊的情況有一個基本的了解。
玩家通過上下左右方向鍵來控制,當按鍵按下的時候,玩家開始向着對應的方向不停的移動,直到撞到牆壁或者按下其他方向鍵轉到其他方向。
在上面這個截圖中,汽車即将接近一個交叉路口。如果使用者不按任何鍵,它将繼續向下移動。然而,如果正好在到達牆的右邊的空隙時按下右鍵,汽車就會向右開。
你被包圍了
是以,汽車如何知道他是否可以轉彎,什麼時候它應該轉彎呢?為了弄清楚這個,我們在汽車四周建立了4塊臨時瓦片。
在 update 函數中,我們将使用Phaser Tilemap類中内建的特性,此特性可用于掃描特殊的瓦片:
this.marker.x = this.math.snapToFloor(Math.floor(this.car.x), 32) / 32;
this.marker.y = this.math.snapToFloor(Math.floor(this.car.y), 32) / 32;
var i = this.layer.index;
var x = this.marker.x;
var y = this.marker.y;
this.directions[Phaser.LEFT] = this.map.getTileLeft(i, x, y);
this.directions[Phaser.RIGHT] = this.map.getTileRight(i, x, y);
this.directions[Phaser.UP] = this.map.getTileAbove(i, x, y);
this.directions[Phaser.DOWN] = this.map.getTileBelow(i, x, y);
Tilemap.getTileLeft
及其同類函數正是用于實作我們上面所說的功能:傳回給定坐标左邊的瓦片(如果沒有找到,則範圍
null
)。
因為這些函數是基于瓦片坐标而不是像素的,是以我們首先要找出我們的汽車的地圖中的确切位置。我們對汽車的x和y值調用
Math.floor
函數,然後使用
Phaser.Math.snapToFloor
轉換為網格坐标。這幾步就會得到汽車所在的确切瓦片。我們把結果存放在
marker
變量中。
周圍四塊瓦片存放在
directions
數組中。啟用渲染調試開關,我們能夠看到汽車知道他四周的相關資訊:
綠色的瓦片表示汽車可以安全的移動過去。紅色的表示是障礙物,白色的表示目前的移動方向。
放置一個轉彎辨別
确認汽車是否可以轉彎是第一個要解決的事情。第二個要解決的是要告訴它什麼時候轉彎,因為我們想要它在快達到地圖合适的位置的時候轉彎,否則,它就會撞到牆上停止向前。
可以利用
checkDirection
函數來實作這個。
這個函數需要傳入一個參數:汽車将要轉彎的方向。這是一個Phaser方向常量,例如
Phaser.LEFT
或
Phaser.DOWN
。
這個函數做的第一件事就判斷是否滿足以下任意一個條件:
- 汽車的運動方向就是要轉向的那個方向
- 那個方向上沒有瓦片
- 那個方向上的瓦片不是一個“安全瓦片”(例如是一堵牆)
如果不滿足上面任意一個條件,接下來就會設定一個轉向标志。這個标志儲存在
turnPoint
中,這是個
Phaser.Point
對象,儲存了我們要汽車改變方向時所在的點的坐标。
if (this.current === this.opposites[turnTo])
{
this.move(turnTo);
}
else
{
this.turning = turnTo;
this.turnPoint.x = (this.marker.x * this.gridsize) + (this.gridsize / 2);
this.turnPoint.y = (this.marker.y * this.gridsize) + (this.gridsize / 2);
}
理想的轉彎點就是汽車目前正駛入的那個方塊的中心。在圖中可以看到一個黃點:
Warm and Fuzzy Inside
當 car.x 和 car.y 與轉彎點的值比對時,汽車将轉向新的方向。如果汽車以每幀一個像素的速度移動的話,那沒問題。但是如果我們想要應用加速效果,或者其他改變汽車速度的方法,就會出問題。因為汽車的x/y坐标将隻會是與轉彎點接近而不是正好相等。
為了解決這個問題,我們使用了
Phaser.Math.fuzzyEqual
這個函數。它接受兩個值和一個門檻值。它将比較兩個值,如果他們在差在門檻值範圍内,他們被認為是相等:
this.math.fuzzyEqual(a, b, threshold)
這正滿足我們的需求。對于速度為150,我們的門檻值可以設定為3。這個值足夠可以保證汽車不會跳過轉彎點。在
update
函數中,我們将檢查汽車是否已經到達了轉彎點。
當汽車到達時,我們将重新設定它的坐标,同時重新設定汽車的剛體的坐标,然後轉向新的方向:
this.car.x = this.turnPoint.x;
this.car.y = this.turnPoint.y;
this.car.body.reset(this.turnPoint.x, this.turnPoint.y);
this.move(this.turning);
this.turning = Phaser.NONE;
通過增加上面的這最後一部分代碼,你現在可以自由的在地圖上行駛,在拐角轉彎,随意的的撞牆。
進入Pacman
讓我們來用上面的工作機制去克隆一個Pacman。一開始的建構是一樣的:一個玩家精靈和一幅瓦片地圖。不同的在于我們的Pacman精靈是動态的:
動畫由 Phaser Animation Manager 來處理:
this.pacman.animations.add('munch', [0, 1, 2, 1], 20, true);
this.pacman.play('munch');
然而,在
move
函數中,我們需要将它的臉轉向新的方向:
this.pacman.scale.x = 1;
this.pacman.angle = 0;
if (direction === Phaser.LEFT)
{
this.pacman.scale.x = -1;
}
else if (direction === Phaser.UP)
{
this.pacman.angle = 270;
}
else if (direction === Phaser.DOWN)
{
this.pacman.angle = 90;
}
我們重設了它的水準縮放和角度,然後基于它的方向進行調整。它的臉預設是朝右的,是以我們可以通過調整
scale.x
來讓它朝左。要讓它朝上或者朝下,就要進行旋轉。
另外一個重要的部分就是:Pacman實際上比網格要大,是以我們需要調整它的剛體以網格大小:
this.pacman.body.setSize(16, 16, 0, 0);
這一句是在精靈(32x32大小)的中心位置設定了一個16x16大小的剛體,這就完美的适應了16x16大小的網格。
豆子
Pacman 需要一些豆子來吃。豆子已經使用了7号瓦片畫到了瓦片地圖上。是以我們将使用Phaser特性來将所有的7号瓦片轉變為精靈。
this.dots = this.add.physicsGroup();
this.map.createFromTiles(7, this.safetile, 'dot', this.layer, this.dots);
this.dots.setAll('x', 6, false, false, 1);
this.dots.setAll('y', 6, false, false, 1);
這裡首先擷取豆子瓦片,用空白瓦片(安全瓦片)來代替,然後為每一個空白瓦片在
dots
組中添加一個精靈。
setAll
函數用來調整豆子精靈的位置,添加6個像素的偏移,因為他們隻有4x4大小,這可以讓他們處于瓦片的中心。
Pacman與豆子之間的碰撞檢測在
update
函數中:
this.physics.arcade.overlap(this.pacman, this.dots, this.eatDot, null, this);
如果他們重疊了,就調用:
eatDot: function (pacman, dot) {
dot.kill();
if (this.dots.total === 0)
{
this.dots.callAll('revive');
}
},
豆子被銷毀了。如果豆子組中的豆子數量變為零,我們要讓豆子重生。是以你可以重新把豆子吃光!
希望你可以看到,我們隻用很少的代碼,我們現在實作了一個最基本的吃豆子遊戲。
Star Bug
基于上面這些代碼,我們利用這些概念将遊戲進一步演變。增加更多的關卡、會飛的敵人和大量的收藏品。最終的結果就是Star Bug遊戲:
這個遊戲将會出現在我們即将面世的 Phaser Book of Games 這本書中。