Phaser 編程技巧,phaser編程技巧
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 這本書中。