天天看點

Phaser 程式設計技巧

2015年2月6日

歡迎!

Phaser 程式設計技巧

在連續幾周讨論了平台跳躍遊戲的機制之後,我們也應該休息一下了。是以,本周的教程将基于論壇上經常談論的特性:網格運動,或者可以說得更加明确一點:如何像Pacman(譯者注:在紅白機上叫做吃豆子,或者小精靈)那樣在網格中移動。

我們本周讨論的代碼可以讓玩家優雅地在瓦片地圖中移動,在很小的空間中轉彎。我們還将建構Pacman遊戲的核心代碼。

擷取源代碼

在這裡我隻會着重高亮最重要的部分代碼。是以請先浏覽一下代碼。如果你對一些代碼有疑問,你可以去論壇詢問。

運作/編輯汽車遊戲代碼可以到 jsbin 或者 codepen (譯者:國内使用者可通路runjs)

運作/編輯Pacman代碼可以到jsbin or codepen (譯者:國内使用者可通路runjs)

克隆Phaser程式設計技巧代碼可以到git repo。

Phaser 程式設計技巧

基礎設施的建構

我們需要一個玩家精靈和一張瓦片地圖。瓦片地圖包含來了關卡的布局。在這裡,我們在Tiled(譯者注:Tiled官網 http://www.mapeditor.org/)中畫了一個簡單的關卡:

Phaser 程式設計技巧

這個軟體導出了一個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);      

目前的問題

玩家的地圖中的移動是一件需要解決的遊戲的挑戰。 盡管表面上這個看起來很簡單,但是實際上這需要玩家對象對周邊的情況有一個基本的了解。

玩家通過上下左右方向鍵來控制,當按鍵按下的時候,玩家開始向着對應的方向不停的移動,直到撞到牆壁或者按下其他方向鍵轉到其他方向。

Phaser 程式設計技巧

在上面這個截圖中,汽車即将接近一個交叉路口。如果使用者不按任何鍵,它将繼續向下移動。然而,如果正好在到達牆的右邊的空隙時按下右鍵,汽車就會向右開。

你被包圍了

是以,汽車如何知道他是否可以轉彎,什麼時候它應該轉彎呢?為了弄清楚這個,我們在汽車四周建立了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

 數組中。啟用渲染調試開關,我們能夠看到汽車知道他四周的相關資訊:

Phaser 程式設計技巧

綠色的瓦片表示汽車可以安全的移動過去。紅色的表示是障礙物,白色的表示目前的移動方向。

放置一個轉彎辨別

确認汽車是否可以轉彎是第一個要解決的事情。第二個要解決的是要告訴它什麼時候轉彎,因為我們想要它在快達到地圖合适的位置的時候轉彎,否則,它就會撞到牆上停止向前。

可以利用 

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);
}      

理想的轉彎點就是汽車目前正駛入的那個方塊的中心。在圖中可以看到一個黃點:

Phaser 程式設計技巧

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;      

通過增加上面的這最後一部分代碼,你現在可以自由的在地圖上行駛,在拐角轉彎,随意的的撞牆。

Phaser 程式設計技巧

進入Pacman

讓我們來用上面的工作機制去克隆一個Pacman。一開始的建構是一樣的:一個玩家精靈和一幅瓦片地圖。不同的在于我們的Pacman精靈是動态的:

Phaser 程式設計技巧

動畫由 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');
    }

},      

豆子被銷毀了。如果豆子組中的豆子數量變為零,我們要讓豆子重生。是以你可以重新把豆子吃光!

Phaser 程式設計技巧

希望你可以看到,我們隻用很少的代碼,我們現在實作了一個最基本的吃豆子遊戲。

Star Bug

基于上面這些代碼,我們利用這些概念将遊戲進一步演變。增加更多的關卡、會飛的敵人和大量的收藏品。最終的結果就是Star Bug遊戲:

Phaser 程式設計技巧

這個遊戲将會出現在我們即将面世的 Phaser Book of Games 這本書中。

繼續閱讀