pixi(入門)

Pixi教程

基於官方教程翻譯;水平有限,若有錯誤歡迎提PR,轉載請註明出處。翻譯者爲htkz(完成了用 Pixi 繪製幾何圖形 和 顯示文本 章節)和zainking(完成了其餘全部章節) 另感謝htkzNearZXH以及HHHHhgqcdxhg對錯誤及錯別字等作出的訂正。javascript

這個教程將要一步步介紹怎麼用Pixi作遊戲或者交互式媒體。這個教程已經升級到 Pixi v4.5.5。若是你喜歡這個教程,你必定也喜歡這本書,它比這個教程多了80%的內容html

目錄:

  1. 介紹
  2. 安裝
    1. 安裝 Pixi
  3. 建立舞臺(stage)和畫布(renderer)
  4. Pixi 精靈
  5. 把圖像加載進紋理緩存
  6. 顯示精靈(sprite)
    1. 使用別名
    2. 一些關於加載的其餘知識
      1. 使用普通的javaScript Img對象或canvas建立一個精靈
      2. 給已經加載的文件設定一個名字
      3. 監視加載進程
      4. 一些關於Pixi的加載器的其餘知識
  7. 定位精靈
  8. 大小和比例
  9. 角度
  10. 從精靈圖(雪碧圖)中獲取精靈
  11. 使用一個紋理貼圖集
  12. 加載紋理貼圖集
  13. 從一個紋理貼圖集建立精靈
  14. 移動精靈
  15. 使用速度屬性
  16. 遊戲狀態
  17. 鍵盤響應
  18. 將精靈分組
    1. 局部位置和全局位置
    2. 使用 ParticleContainer 分組精靈
  19. 用 Pixi 繪製幾何圖形
    1. 矩形
    2. 圓形
    3. 橢圓
    4. 圓角矩形
    5. 多邊形
  20. 顯示文本
  21. 碰撞檢測
    1. 一個 hitTestRectangle 函數
  22. 實例學習: 寶物獵人
    1. 用 setup 函數初始化遊戲
      1. 建立遊戲場景
      2. 建立地牢,門,獵人和寶箱
      3. 建立泡泡怪(這個怪物好萌)
      4. 建立血條
      5. 建立提示文本
    2. 開始遊戲
    3. 移動獵人
      1. 限制移動範圍
    4. 移動泡泡怪們
    5. 碰撞檢測
    6. 處理到達出口和結束遊戲
  23. 一些關於精靈的其餘知識
  24. 展望將來
    i.Hexi
    ii.BabylonJS
  25. 支持這個工程

介紹

Pixi是一個超快的2D渲染引擎。這意味着什麼呢?這意味着它會幫助你用JavaScript或者其餘HTML5技術來顯示媒體,建立動畫或管理交互式圖像,從而製做一個遊戲或應用。它擁有語義化的,簡潔的API接口而且加入了一些很是有用的特性。好比支持紋理貼圖集和爲精靈(交互式圖像)提供了一個簡單的動畫系統。它也提供了一個完備的場景圖,你能夠在精靈圖層裏面建立另外一個精靈,固然也可讓精靈響應你的鼠標或觸摸事件。最重要的的是,Pixi沒有妨礙你的編程方式,你能夠本身選擇使用多少它的功能,你能夠遵循你本身的編碼風格,或讓Pixi與其餘有用的框架無縫集成。html5

Pixi的API事實上比起久經沙場又老舊的Macromedia/Adobe Flash API要精緻。若是你是一個Flash開發者,將會對這樣的API感受更好。其餘的同類渲染框架(好比CreateJS,Starling, Sparrow 和 Apple’s SpriteKit.)也在使用相似的API。Pixi API的優點在於它是通用的:它不是一個遊戲引擎。這是一個優點,由於它給了你全部的自由去作任何你想作的事,甚至用它能夠寫成你本身的遊戲引擎。(譯者:做者這點說的很對,譯者有一個朋友就使用它製做本身的Galgame引擎AVG.js)。java

在這個教程裏,你將會明白怎樣用Pixi的強大的圖片渲染能力和場景圖技術來和作一個遊戲聯繫起來。可是Pixi不只僅能作遊戲 —— 你能用這個技術去建立任何交互式媒體應用。這甚至意味着手機應用。node

你在開始這個教程以前須要知道什麼呢?git

你須要一個對於HTML和JavaScript大體的瞭解。你不必成爲這方面的專家才能開始,即便一個野心勃勃的初學者也能夠開始學習。這本書就是一個學習的好地方:github

Foundation Game Design with HTML5 and JavaScriptweb

我知道這本書是最好的,由於這本書是我寫的!算法

這裏有一些好的代碼來幫助你開始:spring

Khan Academy: Computer Programming

Code Academy: JavaScript

選擇一個屬於你的最好的學習方式吧!

因此,明白了麼?

你知道JavaScript的變量,函數,數組和對象怎麼使用麼?你知道JSON 數據文件是什麼麼? 你用過 Canvas 繪圖 API麼?

爲了使用Pixi,你也須要在你項目的根目錄運行一個web服務器,你知道什麼是web服務器,怎麼在你的項目文件夾裏面運行它麼?最好的方式是使用node.js 而且去用命令行安裝http-server. 不管如何,你須要習慣和Unix命令行一塊兒工做。你能夠在這個視頻中去學習怎樣使用 Unix當你完成時,繼續去學習 這個視頻.你應該學會怎樣用Unix,這是一個頗有趣和簡單的和電腦交互的方式,而且僅僅須要兩個小時。

若是你真的不想用命令行的方式,就嘗試下 Mongoose webserver:

Mongoose

或者來使用Brackets text editor這個使人驚豔的代碼編輯器。他會在你點擊那個「閃電按鈕」的時候自動啓動web服務器和瀏覽器。

如今,若是你以爲你準備好了了,開始吧!

(給讀者的小提示:這是一個 交互式的文檔.若是你有關於特殊細節的任何問題或須要任何澄清均可以建立一個GitHub工程 issue ,我會對這個文檔更新更多信息。)

安裝

在你開始寫任何代碼以前,給你的工程建立一個目錄,而且在根目錄下運行一個web服務器。若是你不這麼作,Pixi不會工做的。

如今,你須要去安裝Pixi。

 

安裝 Pixi

這個教程使用的版本是 v4.5.5 你能夠選擇使用 Pixi v4.5.5的發佈頁面pixi文件夾下的pixi.min.js文件,或者從Pixi的主要發佈頁面中獲取最新版本。

這個文件就是你使用Pixi惟一須要的文件,你能夠忽視全部這個工程的其餘文件,你不須要他們。

如今,建立一個基礎的HTML頁面,用一個<script>標籤去加載你剛剛下載的pixi.min.js文件。<script>標籤的src屬性應該是你根目錄文件的相對路徑————固然請確保你的web服務器在運行。你的<script>標籤應該看起來像是這樣:

<script src="pixi.min.js"></script>

這是你用來連接Pixi和測試它是否工做的基礎頁面。(這裏假設 pixi.min.js在一個叫作pixi的子文件夾中):

<!doctype html>
<html>
<head> <meta charset="utf-8"> <title>Hello World</title> </head> <script src="pixi/pixi.min.js"></script> <body> <script type="text/javascript">  let type = "WebGL"  if(!PIXI.utils.isWebGLSupported()){  type = "canvas"  }   PIXI.utils.sayHello(type)  </script> </body> </html>

若是Pixi鏈接成功,一些這樣的東西會在你的瀏覽器控制檯裏顯示:

PixiJS 4.4.5 - * canvas * http://www.pixijs.com/  ♥♥♥

建立Pixi應用和 舞臺

如今你能夠開始使用Pixi!

可是怎麼用?

第一步就是去建立一個能夠顯示圖片的矩形顯示區。Pixi擁有一個Pixi應用對象來幫助你建立它。它會自動建立一個<canvas>HTML標籤而且計算出怎麼去讓你的圖片在這個標籤中顯示。你如今須要建立一個特殊的Pixi容器對象,他被稱做舞臺。正如你所見,這個舞臺對象將會被看成根容器而使用,它將包裹全部你想用Pixi顯示的東西。

這裏是你須要建立一個名叫app的Pixi應用對象和一個舞臺的必要的代碼。這些代碼須要在你的HTML文檔中以<script>標籤包裹。

//Create a Pixi Application let app = new PIXI.Application({width: 256, height: 256}); //Add the canvas that Pixi automatically created for you to the HTML document document.body.appendChild(app.view);

這是你想要開始使用Pixi的最基本的代碼。它在你的文檔中建立了一個256像素寬高的黑色canvas標籤。當你運行這個代碼的時候瀏覽器應該顯示成這樣:

Basic display

啊哈, 一個 black square!

PIXI.Application算出了應該使用Canvas仍是WebGL去渲染圖象,它取決於你正在使用的瀏覽器支持哪個。它的參數是一個被稱做options的對象。在這兒例子中,它的width 和 height屬性已經被設置了,它們決定了canvas的寬和高(單位是像素)。你可以在options對象中使用更多的屬性設置,這裏展現了你如何使用它來圓滑邊界,設置透明度和分辨率:

let app = new PIXI.Application({ width: 256, // default: 800 height: 256, // default: 600 antialias: true, // default: false transparent: false, // default: false resolution: 1 // default: 1 } );

若是你以爲Pixi的默認設置也不錯,你就不須要做任何的設置,可是若是你須要,就在這裏看一下Pixi的文檔吧:PIXI.Application.

這些設置作了些什麼呢? antialias使得字體的邊界和幾何圖形更加圓滑(WebGL的anti-aliasing在全部平臺都不可用,因此你須要在你的遊戲的標籤平臺上測試他們)。transparent將整個Canvas標籤的透明度進行了設置。resolution讓Pixi在不一樣的分辨率和像素密度的平臺上運行變得簡單。設置分辨率對於這個教程而言有些超綱了,到那時你能夠看Mat Grove'sexplanation之中是如何使用resolution的全部細節的。可是日常,只要保持resolution是1,就能夠應付大多數工程了。

Pixi的畫布對象將會默認選擇WebGL引擎渲染模式,它更快而且可讓你使用一些壯觀的視覺特效————若是你把他們都學了。可是若是你須要強制使用Canvas引擎繪製而拋棄WebGL,你能夠設置forceCanvas選項爲true,像這樣:

forceCanvas: true,

若是你須要在你建立canvas標籤以後改變它的背景色,設置 app.renderer對象的backgroundColor屬性爲一個任何的十六進制顏色:

app.renderer.backgroundColor = 0x061639;

若是你想要去找到畫布的寬高,使用app.renderer.view.width 和app.renderer.view.height

使用畫布resize方法能夠改變canvas的大小,提供任何新的width 和 height變量給他都行。可是爲了確認寬高的格式正確,將autoResize設置爲true

app.renderer.autoResize = true; app.renderer.resize(512, 512);

若是你想讓canvas佔據整個窗口,你能夠將這些CSS代碼放在文檔中,而且刷新你瀏覽器窗口的大小。

app.renderer.view.style.position = "absolute";
app.renderer.view.style.display = "block";
app.renderer.autoResize = true;
app.renderer.resize(window.innerWidth, window.innerHeight);

可是,若是你這麼作了,要記得把padding和margin都設置成0:

<style>* {padding: 0; margin: 0}</style>

(*這個通配符, 是CSS選擇全部HTML元素的意思。)

若是你想要canvas在任何瀏覽器中統一尺寸,你可使用scaleToWindow 成員函數.

Pixi 精靈

如今你就有了一個畫布,能夠開始往上面放圖像了。全部你想在畫布上顯示的東西必須被加進一個被稱做 舞臺的Pixi對象中。你可以像這樣使用舞臺對象:

app.stage

這個舞臺是一個Pixi 容器對象。你能把它理解成一種將放進去的東西分組並存儲的空箱子。 舞臺對象是在你的場景中全部可見對象的根容器。全部你放進去的東西都會被渲染到canvas中。如今舞臺是空的,可是很快咱們就會放進去一點東西。 (你能夠從這瞭解關於Pixi容器對象的更多信息here).

(重要信息:由於舞臺是一個Pixi容器對象,因此他有不少其餘容器對象都有的屬性和方法。可是,儘管舞臺擁有width 和 height屬性, 他們都不能查看畫布窗口的大小 。舞臺的width 和 height屬性僅僅告訴了你你放進去的東西佔用的大小 - 更多的信息在前面!)

因此你能夠放些什麼到舞臺上呢?那就是被稱做 精靈 的特殊圖像對象。精靈是你能用代碼控制圖像的基礎。你可以控制他們的位置,大小,和許多其餘有用的屬性來產生交互和動畫。學習怎樣建立和控制精靈是學習Pixi最重要的部分。若是你知道怎麼建立精靈和把他們添加進舞臺,離作出一個遊戲就僅僅剩下一步之遙!

Pixi擁有一個精靈類來建立遊戲精靈。有三種主要的方法來建立它:

  • 用一個單圖像文件建立。
  • 用一個 雪碧圖 來建立。雪碧圖是一個放入了你遊戲所需的全部圖像的大圖。
  • 從一個紋理貼圖集中建立。(紋理貼圖集就是用JSON定義了圖像大小和位置的雪碧圖)

你將要學習這三種方式,可是在開始以前,你得弄明白圖片怎麼用Pixi顯示。

將圖片加載到紋理緩存中

由於Pixi用WebGL和GPU去渲染圖像,因此圖像須要轉化成GPU能夠處理的版本。能夠被GPU處理的圖像被稱做 紋理 。在你讓精靈顯示圖片以前,須要將普通的圖片轉化成WebGL紋理。爲了讓全部工做執行的快速有效率,Pixi使用 紋理緩存 來存儲和引用全部你的精靈須要的紋理。紋理的名稱字符串就是圖像的地址。這意味着若是你有從"images/cat.png"加載的圖像,你能夠在紋理緩存中這樣找到他:

PIXI.utils.TextureCache["images/cat.png"];

紋理被以WEBGL兼容的格式存儲起來,它可使Pixi的渲染有效率的進行。你如今可使用Pixi的精靈類來建立一個新的精靈,讓它使用紋理。

let texture = PIXI.utils.TextureCache["images/anySpriteImage.png"]; let sprite = new PIXI.Sprite(texture);

可是你該怎麼加載圖像並將它轉化成紋理?答案是用Pixi已經構建好的loader對象。

Pixi強大的loader對象能夠加載任何你須要種類的圖像資源。這裏展現了怎麼加載一個圖像並在加載完成時用一個叫作setup的方法來使用它。

PIXI.loader .add("images/anyImage.png") .load(setup); function setup() { //This code will run when the loader has finished loading the image }

Pixi的最佳實踐 若是你使用了Loader,你就應該建立一個精靈來鏈接loaderresources對象,像下面這樣:

let sprite = new PIXI.Sprite( PIXI.loader.resources["images/anyImage.png"].texture );

這裏是一個完整的加載圖像的代碼。調用setup方法,並未加載的圖像建立一個精靈。

PIXI.loader .add("images/anyImage.png") .load(setup); function setup() { let sprite = new PIXI.Sprite( PIXI.loader.resources["images/anyImage.png"].texture ); }

這是這個教程之中用來加載圖像和建立精靈的通用方法。

你能夠鏈式調用add方法來加載一系列圖像,像下面這樣:

PIXI.loader .add("images/imageOne.png") .add("images/imageTwo.png") .add("images/imageThree.png") .load(setup);

更好的方式則是用數組給一個add方法傳參,像這樣:

PIXI.loader .add([ "images/imageOne.png", "images/imageTwo.png", "images/imageThree.png" ]) .load(setup);

這個loader也容許你使用JSON文件,關於JSON文件你應該已經在前面學過了。

顯示精靈

在你加載一個圖像以後,能夠用它來建立一個精靈,你須要用stage.addChild方法把它放到Pixi的舞臺上面去,像這樣:

app.stage.addChild(cat);

記住,舞臺是用來包裹你全部精靈的主要容器。

重點:你不該該看見任何沒被加入舞臺的精靈

在咱們繼續以前,讓咱們看一個怎樣使用顯示一個單圖像的例子。在examples/images文件夾中,你將找到一個64*64像素大小的貓的PNG圖像文件。

基礎顯示圖像文件

這裏是全部的顯示一個圖像,建立一個精靈,顯示在Pixi的舞臺上所須要的代碼。

//Create a Pixi Application let app = new PIXI.Application({ width: 256, height: 256, antialias: true, transparent: false, resolution: 1 } ); //Add the canvas that Pixi automatically created for you to the HTML document document.body.appendChild(app.view); //load an image and run the `setup` function when it's done PIXI.loader .add("images/cat.png") .load(setup); //This `setup` function will run when the image has loaded function setup() { //Create the cat sprite let cat = new PIXI.Sprite(PIXI.loader.resources["images/cat.png"].texture); //Add the cat to the stage app.stage.addChild(cat); }

程序跑起來,你會看到:

在舞臺上的小貓咪

如今咱們已經取得了一些進展!

若是你想把一個精靈從舞臺上挪走,就可使用removeChild方法:

app.stage.removeChild(anySprite)

可是一般,咱們都把精靈的visible屬性設置成false來讓精靈簡單的隱藏。

anySprite.visible = false;

 

使用別名

你能夠對你使用頻繁的Pixi對象和方法設置一些簡略的可讀性更強的別名。舉個例子,你想給全部的Pixi對象增長PIXI前綴麼?若是你這樣想,那就建立一個簡短的別名給他吧。下面是一個給TextureCache對象建立別名的例子:

let TextureCache = PIXI.utils.TextureCache

如今就能夠像這樣使用別名了:

let texture = TextureCache["images/cat.png"];

使用別名給寫出簡潔的代碼提供了額外的好處:他幫助你緩存了Pixi的經常使用API。若是Pixi的API在未來的版本里改變了 - 沒準他真的會變! - 你將會須要在一個地方更新這些對象和方法,你只用在工程的開頭而不是全部的實例那裏!因此Pixi的開發團隊想要改變它的時候,你只用一步便可完成這個操做!

來看看怎麼將全部的Pixi對象和方法改爲別名以後,來重寫加載和顯示圖像的代碼。

//Aliases let Application = PIXI.Application, loader = PIXI.loader, resources = PIXI.loader.resources, Sprite = PIXI.Sprite; //Create a Pixi Application let app = new Application({ width: 256, height: 256, antialias: true, transparent: false, resolution: 1 } ); //Add the canvas that Pixi automatically created for you to the HTML document document.body.appendChild(app.view); //load an image and run the `setup` function when it's done loader .add("images/cat.png") .load(setup); //This `setup` function will run when the image has loaded function setup() { //Create the cat sprite let cat = new Sprite(resources["images/cat.png"].texture); //Add the cat to the stage app.stage.addChild(cat); } 

大多數教程中的例子將會使用Pixi的別名來處理。除非另有說明,不然你能夠假定下面全部的代碼都使用了這些別名。

這就是你須要的全部的關於加載圖像和建立精靈的知識。

 

一些關於加載的其餘知識

咱們的例子中的格式是加載圖像和顯示精靈的最佳實踐。因此你能夠安全的忽視這些章節直接看"定位精靈"。可是Pixi的加載器有一些你不經常使用的複雜功能。

 

使用普通的javaScript Img對象或canvas建立一個精靈

爲了優化和效率咱們經常選擇從預加載的紋理緩存的紋理之中建立精靈。可是若是由於某些緣由你須要從JavaScript的Image對象之中建立,你可使用Pixi的BaseTextureTexture類:

let base = new PIXI.BaseTexture(anyImageObject), texture = new PIXI.Texture(base), sprite = new PIXI.Sprite(texture);

你可使用BaseTexture.fromCanvas從任何已經存在canvas標籤中建立紋理:

let base = new PIXI.BaseTexture.fromCanvas(anyCanvasElement),

若是你想改變已經顯示的精靈的紋理,使用texture屬性,能夠設置任何Texture對象,像下面這樣:

anySprite.texture = PIXI.utils.TextureCache["anyTexture.png"];

你可使用這個技巧在遊戲發生一些重大變化時交互式的改變精靈的紋理。

 

給加載的文件設置別名

你能夠給任何你加載的源文件分配一個獨一無二的別名。你只須要在add方法中第一個參數位置傳進去這個別名就好了,舉例來講,下面實現了怎麼給這個貓的圖片重命名爲catImage

PIXI.loader .add("catImage", "images/cat.png") .load(setup);

這種操做在loader.resources中建立了一個叫作catImage的對象。 這意味着你能夠建立一個引用了catImage對象的精靈,像這樣:

let cat = new PIXI.Sprite(PIXI.loader.resources.catImage.texture);

然而,我建議你永遠別用這個操做!由於你將不得不記住你全部加載文件的別名,並且必須確信你只用了它們一次,使用路徑命名,咱們將將這些事情處理的更簡單和更少錯誤。

 

監視加載進程

Pixi的加載器有一個特殊的progress事件,它將會調用一個能夠定製的函數,這個函數將在每次文件加載時調用。progress事件將會被loaderon方法調用,像是這樣:

PIXI.loader.on("progress", loadProgressHandler);

這裏展現了怎麼將on方法注入加載鏈中,而且每當文件加載時調用一個用戶定義的名叫loadProgressHandler的函數。

PIXI.loader .add([ "images/one.png", "images/two.png", "images/three.png" ]) .on("progress", loadProgressHandler) .load(setup); function loadProgressHandler() { console.log("loading"); } function setup() { console.log("setup"); }

每個文件加載,progress事件調用loadProgressHandler函數在控制檯輸出 "loading"。當三個文件都加載完畢,setup方法將會運行,下面是控制檯的輸出:

loading
loading
loading
setup

這就不錯了,不過還能變的更好。你能夠知道哪一個文件被加載了以及有百分之多少的文件被加載了。你能夠在loadProgressHandler增長loader參數和resource參數實現這個功能,像下面這樣:

function loadProgressHandler(loader, resource) { /*...*/ }

你如今可使用 resource.url變量來找到如今已經被加載的文件。(若是你想找到你定義的別名,使用resource.name參數。)你可使用loader.progress來找到如今有百分之多少的文件被加載了,這裏有一些關於上面描述的代碼:

PIXI.loader .add([ "images/one.png", "images/two.png", "images/three.png" ]) .on("progress", loadProgressHandler) .load(setup); function loadProgressHandler(loader, resource) { //Display the file `url` currently being loaded console.log("loading: " + resource.url); //Display the percentage of files currently loaded console.log("progress: " + loader.progress + "%"); //If you gave your files names as the first argument //of the `add` method, you can access them like this //console.log("loading: " + resource.name); } function setup() { console.log("All files loaded"); }

這裏是程序運行後的控制檯顯示:

loading: images/one.png progress: 33.333333333333336% loading: images/two.png progress: 66.66666666666667% loading: images/three.png progress: 100% All files loaded

這實在太酷了!由於你能用這個玩意作個進度條出來。 (注意:還有一些額外的resource對象屬性, resource.error會告訴你有哪些加載時候的錯誤,resource.data將會給你文件的原始二進制數據。)

 

一些關於Pixi的加載器的其餘知識

Pixi的加載器有不少能夠設置的功能,讓我速覽一下:

add 方法有四個基礎參數:

add(name, url, optionObject, callbackFunction)

這裏有文檔裏面對這些參數的描述:

name (string): 加載源文件的別名,若是沒設置,url就會被放在這.
url (string): 源文件的地址,是加載器 baseUrl的相對地址.
options (object literal): 加載設置.
options.crossOrigin (Boolean): 源文件請求跨域不?默認是自動設定的。
options.loadType: 源文件是怎麼加載進來的?默認是Resource.LOAD_TYPE.XHR。 options.xhrType: 用XHR的時候該怎麼處理數據? 默認是Resource.XHR_RESPONSE_TYPE.DEFAULT
callbackFunction: 當這個特定的函數加載完,這個特定的函數將會被執行。

只有url必填(你總得加載個文件吧。)

這裏有點用了add方法加載文件的例子。第一個就是文檔裏所謂的「正常語法」:

.add('key', 'http://...', function () {}) .add('http://...', function () {}) .add('http://...')

這些就是所謂「對象語法」啦:

.add({
  name: 'key2', url: 'http://...' }, function () {}) .add({ url: 'http://...' }, function () {}) .add({ name: 'key3', url: 'http://...' onComplete: function () {} }) .add({ url: 'https://...', onComplete: function () {}, crossOrigin: true })

你也能夠給add方法傳一個對象的數組,或者既使用對象數組,又使用鏈式加載:

.add([
  {name: 'key4', url: 'http://...', onComplete: function () {} }, {url: 'http://...', onComplete: function () {} }, 'http://...' ]);

(注意:若是你須要從新加載一批文件,調用加載器的reset方法:PIXI.loader.reset();

Pixi的加載器還有許多其餘的高級特性,包括可讓你加載和解析全部類型二進制文件的選項。這些並不是你天天都要作的,也超出了這個教程的範圍,因此從GitHub項目中獲取更多信息吧!

精靈位置

如今你知道了怎麼建立和顯示一個精靈,讓咱們學習如何定位他們的位置和改變他們的大小 在最先的示例裏那個貓的精靈被放在了舞臺的左上角。它的xy座標都是0。你能夠經過改變它的xy座標的值來改變他們的位置。下面的例子就是你經過設置xy爲96座標讓它在舞臺上居中。

cat.x = 96; cat.y = 96;

在你建立這個精靈以後,把這兩行代碼放進setup方法。

function setup() { //Create the `cat` sprite let cat = new Sprite(resources["images/cat.png"].texture); //Change the sprite's position cat.x = 96; cat.y = 96; //Add the cat to the stage so you can see it app.stage.addChild(cat); }

(注意:在這個例子裏,Sprite是 PIXI.Sprite的別名,TextureCachePIXI.utils.TextureCache的別名,resourcesPIXI.loader.resources的別名,我從如今開始在代碼中使用這些別名。)

這兩行代碼將把貓往右移動96像素,往下移動96像素。

Cat centered on the stage

這隻貓的左上角(它的左耳朵)(譯者注:從貓的角度看實際上是它的右耳朵。。。)表示了它的x 和 y 座標點。爲了讓他向右移動,增長x這個屬性的值,爲了讓他向下移動,就增長y屬性的值。若是這隻貓的x屬性爲0,他就呆在舞臺的最左邊,若是他的y屬性爲0,他就呆在舞臺的最上邊。

Cat centered on the stage - diagram

你能夠一句話設置精靈的xy:

sprite.position.set(x, y)

大小和比例

你可以經過精靈的widthheight屬性來改變它的大小。這是怎麼把width調整成80像素,height調整成120像素的例子:

cat.width = 80; cat.height = 120;

setup函數裏面加上這兩行代碼,像這樣:

function setup() { //Create the `cat` sprite let cat = new Sprite(resources["images/cat.png"].texture); //Change the sprite's position cat.x = 96; cat.y = 96; //Change the sprite's size cat.width = 80; cat.height = 120; //Add the cat to the stage so you can see it app.stage.addChild(cat); }

結果看起來是這樣:

Cat's height and width changed

你能看見,這隻貓的位置(左上角的位置)沒有改變,只有寬度和高度改變了。

Cat's height and width changed - diagram

精靈都有scale.x 和 scale.y屬性,他們能夠成比例的改變精靈的寬高。這裏的例子把貓的大小變成了一半:

cat.scale.x = 0.5; cat.scale.y = 0.5;

Scale的值是從0到1之間的數字的時候,表明了它對於原來精靈大小的百分比。1意味着100%(原來的大小),因此0.5意味着50%(一半大小)。你能夠把這個值改成2,這就意味着讓精靈的大小成倍增加。像這樣:

cat.scale.x = 2; cat.scale.y = 2;

Pixi能夠用一行代碼縮放你的精靈,那要用到scale.set方法。

cat.scale.set(0.5, 0.5);

若是你喜歡這種,就用吧!

旋轉

你能夠經過對一個精靈的rotation設定一個角度來旋轉它。

cat.rotation = 0.5;

可是旋轉是針對於哪個點發生的呢? 你已經瞭解了,精靈的左上角表明它的位置,這個點被稱之爲 錨點 。若是你用像0.5這種值設定rotation,這個旋轉將會 圍繞着錨點發生 。下面這張圖就是結果:

Rotation around anchor point - diagram

你能看見錨點是貓的左邊耳朵(譯者:對貓來講其實是它的右耳朵!),那裏成了貓的圖片的旋轉中心。 你該怎麼改變錨點呢?經過改變精靈的anchor屬性的xy值來實現。像下面這樣:

cat.anchor.x = 0.5; cat.anchor.y = 0.5;

anchor.xanchor.y的值若是是從0到1,就會被認爲是整個紋理的長度或寬度百分比。設置他們都爲0.5,錨點就處在了圖像中心。精靈定位的依據點不會改變,錨點的改變是另一回事。

下面的圖顯示把錨點居中之後旋轉的精靈。

Rotation around centered anchor point - diagram

你能夠看到精靈的紋理向左移動了,這是個必須記住的重要反作用!

像是positionscale屬性同樣,你也能夠在一行內像這樣設置錨點的位置:

cat.anchor.set(x, y)

精靈也提供和anchor差很少的pivot屬性來設置精靈的原點。若是你改變了它的值以後旋轉精靈,它將會圍繞着你設置的原點來旋轉。舉個例子,下面的代碼將精靈的pivot.xpivot.y設置爲了32。

cat.pivot.set(32, 32)

假設精靈圖是64x64像素,它將繞着它的中心點旋轉。可是記住:你若是改變了精靈的pivot屬性,你也就改變了它的原點位置。

因此anchor 和 pivot的不一樣之處在哪裏呢?他們真的很像!anchor改變了精靈紋理的圖像原點,用0到1的數據來填充。pivot則改變了精靈的原點,用像素的值來填充。你要用哪一個取決於你。兩個都試試就知道哪一個對你而言最適合。

從精靈圖(雪碧圖)中建立精靈【爲了防止與精靈混淆,我在以後的譯文中都將採用雪碧圖這一譯法】

你如今已經知道了怎麼從一個單文件內加載圖像。可是做爲一個遊戲設計師,你沒準更常用 雪碧圖(也被稱之爲 精靈圖)。Pixi封裝了一些方便的方式來處理這種狀況。所謂雪碧圖就是用一個單文件包含你遊戲中須要的全部文件,這裏就是一個包含了遊戲對象和遊戲角色的雪碧圖。

An example tileset

整個雪碧圖是192192像素寬高,但每個單圖像只佔有一個3232的網格。把你的全部遊戲圖像存儲在一個雪碧圖上是一個很是有效率和工程化的手段,Pixi爲此作出了優化。你能夠從一個雪碧圖中用一個矩形區域捕獲一個子圖像。這個矩形擁有和你想提取的子圖像同樣的大小和位置。這裏有一個怎麼從一個精靈圖中獲取「火箭」這個子圖像的例子。

Rocket extracted from tileset

讓咱們看看這部分的代碼,用Pixi的加載器加載tileset.png,就像你在以前的示例之中作到的那樣。

loader
  .add("images/tileset.png") .load(setup);

如今,在圖像被加載以後,用一個矩形塊去截取雪碧圖來建立精靈的紋理。下面是提取火箭,建立精靈,在canvas上顯示它的代碼。

function setup() { //Create the `tileset` sprite from the texture let texture = TextureCache["images/tileset.png"]; //Create a rectangle object that defines the position and //size of the sub-image you want to extract from the texture //(`Rectangle` is an alias for `PIXI.Rectangle`) let rectangle = new Rectangle(192, 128, 64, 64); //Tell the texture to use that rectangular section texture.frame = rectangle; //Create the sprite from the texture let rocket = new Sprite(texture); //Position the rocket sprite on the canvas rocket.x = 32; rocket.y = 32; //Add the rocket to the stage app.stage.addChild(rocket); //Render the stage renderer.render(stage); }

它是如何工做的呢?

Pixi內置了一個通用的Rectangle對象 (PIXI.Rectangle),他是一個用於定義矩形形狀的通用對象。他須要一些參數,前兩個參數定義了x 和y軸座標位置,後兩個參數定義了矩形的width 和 height,下面是新建一個Rectangle對象的格式。

let rectangle = new PIXI.Rectangle(x, y, width, height);

這個矩形對象僅僅是一個 數據對象,如何使用它徹底取決於你。在咱們的例子裏,咱們用它來定義子圖像在雪碧圖中的位置和大小。Pixi的紋理中有一個叫作frame的頗有用的屬性,它能夠被設置成任何的Rectangle對象。frame將紋理映射到Rectangle的維度。下面是怎麼用frame來定義火箭的大小和位置。

let rectangle = new Rectangle(192, 128, 64, 64); texture.frame = rectangle;

你如今能夠用它裁切紋理來建立精靈了:

let rocket = new Sprite(texture);

如今成功了! 由於從一個雪碧圖建立精靈的紋理是一個用的很頻繁的操做,Pixi有一個更加合適的方式來幫助你處理這件事情。欲知後事如何,且聽下回分解。

使用一個紋理貼圖集

若是你正在處理一個很大的,很複雜的遊戲,你想要找到一種快速有效的方式來從雪碧圖建立精靈。紋理貼圖集 就會顯得頗有用處,一個紋理貼圖集就是一個JSON數據文件,它包含了匹配的PNG雪碧圖的子圖像的大小和位置。若是你使用了紋理貼圖集,那麼想要顯示一個子圖像只須要知道它的名字就好了。你能夠任意的排序你的排版,JSON文件會保持他們的大小和位置不變。這很是方便,由於這意味着圖片的位置和大小沒必要寫在你的代碼裏。若是你想要改變紋理貼圖集的排版,相似增長圖片,修改圖片大小和刪除圖片這些操做,只須要修改那個JSON數據文件就好了,你的遊戲會自動給程序內的全部數據應用新的紋理貼圖集。你不必在全部用到它代碼的地方修改它。

Pixi兼容著名軟件Texture Packer輸出的標準紋理貼圖集格式。Texture Packer的基本功能是免費的。讓咱們來學習怎麼用它來製做一個紋理貼圖集,並把它加載進Pixi吧!(你也不是非得用它,還有一些相似的工具輸出的紋理貼圖集Pixi也是兼容的,例如:Shoeboxspritesheet.js。)

首先,從你要用在遊戲的圖片文件們開始。

圖片文件

在這個章節全部的圖片都是被Lanea Zimmerman創做的。你能在他的藝術工做室裏面找到更多相似的東西:這裏,謝謝你,Lanea!

下面,打開Texture Packer,選擇 JSON Hash 框架類型。把你的圖片放進Texture Packer的工做區。(你也能夠把Texture Packer放進包含你圖片的文件夾裏面去。)他將自動的把你的圖片們生成單個圖片文件,而且將他們的原始名稱命名爲紋理貼圖集中的圖片名稱。

圖片文件

若是你正在用免費版的Texture Packer,把 Algorithm 選項設爲Basic,把 Trim mode 選項設爲None,把 Extrude 選項設爲0,把 Size constraints 選項設爲 Any size ,把 PNG Opt Level 中全部的東西都滑到左邊的 0位置。這就可使得Texture Packer正常的輸出你的紋理貼圖集。

若是你作完了,點擊 Publish 按鈕。選擇輸出文件名和存儲地址,把生成文件保存起來。你將會得到兩個文件:一個叫作treasureHunter.json,另一個就是treasureHunter.png。爲了讓目錄乾淨些,咱們把他倆都放到一個叫作images的文件夾裏面去。(你能夠認爲那個json文件是圖片文件的延伸,因此把他們放進一個文件夾是頗有意義的。)那個JSON文件裏面寫清楚了每個子圖像的名字,大小和位置。下面描述了「泡泡怪」這個怪物的子圖像的信息。

"blob.png": { "frame": {"x":55,"y":2,"w":32,"h":24}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":32,"h":24}, "sourceSize": {"w":32,"h":24}, "pivot": {"x":0.5,"y":0.5} },

treasureHunter.json裏面也包含了「dungeon.png」, 「door.png」, "exit.png", 和 "explorer.png"的數據信息,並以和上面相似的信息記錄。這些子圖像每個都被叫作 幀 ,有了這些數據你就不用去記每個圖片的大小和位置了,你惟一要作的就只是肯定精靈的 幀ID 便可。幀ID就是那些圖片的原始名稱,相似"blob.png"或者 "explorer.png"這樣。

使用紋理貼圖集的巨大優點之一就是你能夠很輕易的給每個圖像增長兩個像素的內邊距。Texture Packer默認這麼作。這對於保護圖像的 出血(譯者:出血是排版和圖片處理方面的專有名詞,指在主要內容周圍留空以便印刷或裁切)來講很重要。出血對於防止兩個圖片相鄰而相互影響來講很重要。這種狀況每每發生於你的GPU渲染某些圖片的時候。把邊上的一兩個像素加上去仍是不要?這對於每個GPU來講都有不一樣的作法。因此對每個圖像空出一兩個像素對於顯示來講是最好的兼容。

(注意:若是你真的在每一個圖像的周圍留了兩個像素的出血,你必須時時刻刻注意Pixi顯示時候「丟了一個像素」的狀況。嘗試着去改變紋理的規模模式來從新計算它。texture.baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;,這每每發生於你的GPU浮點運算湊整失敗的時候。)

如今你明白了怎麼建立一個紋理貼圖集,來學習怎麼把他加載進你的遊戲之中吧。

加載紋理貼圖集

可使用Pixi的loader來加載紋理貼圖集。若是是用Texture Packer生成的JSON,loader會自動讀取數據,並對每個幀建立紋理。下面就是怎麼用loader來加載treasureHunter.json。當它成功加載,setup方法將會執行。

loader
  .add("images/treasureHunter.json") .load(setup);

如今每個圖像的幀都被加載進Pixi的紋理緩存之中了。你可使用Texture Packer中定義的他們的名字來取用每個紋理。

從已經加載的紋理貼圖集中建立精靈

一般Pixi給你三種方式從已經加載的紋理貼圖集中建立精靈:

  1. 使用 TextureCache:
let texture = TextureCache["frameId.png"], sprite = new Sprite(texture);
  1. 若是你是使用的 loader來加載紋理貼圖集, 使用loader的 resources:
let sprite = new Sprite( resources["images/treasureHunter.json"].textures["frameId.png"] );
  1. 要建立一個精靈須要輸入太多東西了! 因此我建議你給紋理貼圖集的textures對象建立一個叫作id的別名,象是這樣:
let id = PIXI.loader.resources["images/treasureHunter.json"].textures;

如今你就能夠像這樣實例化一個精靈了:

let sprite = new Sprite(id["frameId.png"]);

真不錯啊~!

這裏在setup函數中用三種不一樣的建立方法建立和顯示了dungeonexplorer, 和 treasure精靈。

//Define variables that might be used in more //than one function let dungeon, explorer, treasure, id; function setup() { //There are 3 ways to make sprites from textures atlas frames //1. Access the `TextureCache` directly let dungeonTexture = TextureCache["dungeon.png"]; dungeon = new Sprite(dungeonTexture); app.stage.addChild(dungeon); //2. Access the texture using throuhg the loader's `resources`: explorer = new Sprite( resources["images/treasureHunter.json"].textures["explorer.png"] ); explorer.x = 68; //Center the explorer vertically explorer.y = app.stage.height / 2 - explorer.height / 2; app.stage.addChild(explorer); //3. Create an optional alias called `id` for all the texture atlas //frame id textures. id = PIXI.loader.resources["images/treasureHunter.json"].textures; //Make the treasure box using the alias treasure = new Sprite(id["treasure.png"]); app.stage.addChild(treasure); //Position the treasure next to the right edge of the canvas treasure.x = app.stage.width - treasure.width - 48; treasure.y = app.stage.height / 2 - treasure.height / 2; app.stage.addChild(treasure); }

這裏是代碼運行的結果:

Explorer, dungeon and treasure

舞臺定義爲512像素見方的大小,你能夠看到代碼中app.stage.heightapp.stage.width屬性使得精靈們排成了一排。下面的代碼使得explorery屬性垂直居中了。

explorer.y = app.stage.height / 2 - explorer.height / 2;

學會使用紋理貼圖集來建立一個精靈是一個基本的操做。因此在咱們繼續以前,你來試着寫一些這樣的精靈吧:blob們和exit的門,讓他們看起來象是這樣:

All the texture atlas sprites

下面就是全部的代碼啦。我也把HTML放了進來,如今你能夠看見全部的上下文。(你能夠在examples/spriteFromTextureAtlas.html找到能夠用於演示的代碼。)注意,blob精靈是用一個循環加進舞臺的,而且他有一個隨機的位置。

<!doctype html> <meta charset="utf-8"> <title>Make a sprite from a texture atlas</title> <body> <script src="../pixi/pixi.min.js"></script> <script> //Aliases let Application = PIXI.Application, Container = PIXI.Container, loader = PIXI.loader, resources = PIXI.loader.resources, TextureCache = PIXI.utils.TextureCache, Sprite = PIXI.Sprite, Rectangle = PIXI.Rectangle; //Create a Pixi Application let app = new Application({ width: 512, height: 512, antialias: true, transparent: false, resolution: 1 } ); //Add the canvas that Pixi automatically created for you to the HTML document document.body.appendChild(app.view); //load a JSON file and run the `setup` function when it's done loader .add("images/treasureHunter.json") .load(setup); //Define variables that might be used in more //than one function let dungeon, explorer, treasure, door, id; function setup() { //There are 3 ways to make sprites from textures atlas frames //1. Access the `TextureCache` directly let dungeonTexture = TextureCache["dungeon.png"]; dungeon = new Sprite(dungeonTexture); app.stage.addChild(dungeon); //2. Access the texture using throuhg the loader's `resources`: explorer = new Sprite( resources["images/treasureHunter.json"].textures["explorer.png"] ); explorer.x = 68; //Center the explorer vertically explorer.y = app.stage.height / 2 - explorer.height / 2; app.stage.addChild(explorer); //3. Create an optional alias called `id` for all the texture atlas //frame id textures. id = PIXI.loader.resources["images/treasureHunter.json"].textures; //Make the treasure box using the alias treasure = new Sprite(id["treasure.png"]); app.stage.addChild(treasure); //Position the treasure next to the right edge of the canvas treasure.x = app.stage.width - treasure.width - 48; treasure.y = app.stage.height / 2 - treasure.height / 2; app.stage.addChild(treasure); //Make the exit door door = new Sprite(id["door.png"]); door.position.set(32, 0); app.stage.addChild(door); //Make the blobs let numberOfBlobs = 6, spacing = 48, xOffset = 150; //Make as many blobs as there are `numberOfBlobs` for (let i = 0; i < numberOfBlobs; i++) { //Make a blob let blob = new Sprite(id["blob.png"]); //Space each blob horizontally according to the `spacing` value. //`xOffset` determines the point from the left of the screen //at which the first blob should be added. let x = spacing * i + xOffset; //Give the blob a random y position //(`randomInt` is a custom function - see below) let y = randomInt(0, app.stage.height - blob.height); //Set the blob's position blob.x = x; blob.y = y; //Add the blob sprite to the stage app.stage.addChild(blob); } } //The `randomInt` helper function function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } </script> </body>

你能夠看見全部的泡泡怪都用一個for循環被建立了,每個泡泡怪都有一個獨一無二的x座標,像是下面這樣:

let x = spacing * i + xOffset; blob.x = x;

spacing變量的值是48,xOffset的值是150。這意味着第一個blob怪的位置的x座標將會是150。這個偏移使得泡泡怪離舞臺左邊的距離有150個像素。每個泡泡怪都有個48像素的空餘,也就是說每個泡泡怪都會比在循環之中前一個建立的泡泡怪的位置的x座標多出48像素以上的增量。它使得泡泡怪們相互間隔,從地牢地板的左邊排向右邊。 每個blob也被賦予了一個隨機的y座標,這裏是處理這件事的代碼:

let y = randomInt(0, stage.height - blob.height); blob.y = y;

泡泡怪的y座標將會從0到512之間隨機取值,它的變量名是stage.height。它的值是利用randomInt函數來獲得的。randomInt返回一個由你定義範圍的隨機數。

randomInt(lowestNumber, highestNumber)

這意味着若是你想要一個1到10之間的隨機數,你能夠這樣獲得它:

let randomNumber = randomInt(1, 10);

這是randomInt方法的定義:

function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }

randomInt是一個很好的用來作遊戲的工具函數,我常常用他。

移動精靈

如今你知道了如何展現精靈,可是讓它們移動呢?很簡單:使用Pixi的ticker。這被稱爲 遊戲循環 。任何在遊戲循環裏的代碼都會1秒更新60次。你能夠用下面的代碼讓 cat 精靈以每幀1像素的速率移動。

function setup() { //Start the game loop by adding the `gameLoop` function to //Pixi's `ticker` and providing it with a `delta` argument. app.ticker.add(delta => gameLoop(delta)); } function gameLoop(delta){ //Move the cat 1 pixel cat.x += 1; }

若是你運行了上面的代碼,你會看到精靈逐步地移動到舞臺的一邊。

Moving sprites

由於每當開始 遊戲循環 的時候,都會爲這隻貓增長1像素的x軸位移。

cat.x += 1;

每個你放進Pixi的ticker的函數都會每秒被執行60次。你能夠看見函數裏面提供了一個delta的內容,他是什麼呢?

delta的值表明幀的部分的延遲。你能夠把它添加到cat的位置,讓cat的速度和幀率無關。下面是代碼:

cat.x += 1 + delta;

是否加進去這個delta的值實際上是一種審美的選擇。它每每只在你的動畫無法跟上60幀的速率時候出現(好比你的遊戲運行在很老舊的機器上)。教程裏面不會用到delta變量,可是若是你想用就盡情的用吧。

你也不必非得用Pixi的ticker來建立遊戲循環。若是你喜歡,也能夠用requestAnimationFrame像這樣建立:

function gameLoop() { //Call this `gameLoop` function on the next screen refresh //(which happens 60 times per second) requestAnimationFrame(gameLoop); //Move the cat cat.x += 1; } //Start the loop gameLoop(); 

隨你喜歡。

這就是移動的所有。只要在循環中改變精靈的一點點屬性,它們就會開始相應的動畫。若是你想讓它往相反的方向移動,只要給它一個負值,像 -1。

你能在 movingSprites.html 文件中找到這段代碼 - 這是所有的代碼:

//Aliases let Application = PIXI.Application, Container = PIXI.Container, loader = PIXI.loader, resources = PIXI.loader.resources, TextureCache = PIXI.utils.TextureCache, Sprite = PIXI.Sprite, Rectangle = PIXI.Rectangle; //Create a Pixi Application let app = new Application({ width: 256, height: 256, antialias: true, transparent: false, resolution: 1 } ); //Add the canvas that Pixi automatically created for you to the HTML document document.body.appendChild(app.view); loader .add("images/cat.png") .load(setup); //Define any variables that are used in more than one function let cat; function setup() { //Create the `cat` sprite cat = new Sprite(resources["images/cat.png"].texture); cat.y = 96; app.stage.addChild(cat); //Start the game loop app.ticker.add(delta => gameLoop(delta)); } function gameLoop(delta){ //Move the cat 1 pixel cat.x += 1; //Optionally use the `delta` value //cat.x += 1 + delta; }

(注意 cat 變量須要在setup 和 gameLoop函數以外定義,而後你能夠在全局中任何地方都能獲取到它們)

你可讓精靈的位置,角度或者大小動起來 - 什麼均可以!你會在下面看到更多精靈動畫的例子。

使用速度屬性

爲了給你更多的靈活性,這裏有兩個 速度屬性 :vx和 vy去控制精靈的運動速度。 vx被用來設置精靈在x軸(水平)的速度和方向。vy被用來設置精靈在y軸(垂直)的速度和方向。 他們能夠直接更新速度變量而且給精靈設定這些速度值。這是一個用來讓你更方便的更新交互式動畫的額外的模塊。

第一步是給你的精靈建立vxvy屬性,而後給他們初始值。

cat.vx = 0; cat.vy = 0;

vxvy設置爲0表示精靈靜止。

接下來,在遊戲循環中,更新vxvy爲你想讓精靈移動的速度值。而後把這些值賦給精靈的xy屬性。下面的代碼講明瞭你如何利用該技術讓cat可以每幀向右下方移動一個像素:

function setup() { //Create the `cat` sprite cat = new Sprite(resources["images/cat.png"].texture); cat.y = 96; cat.vx = 0; cat.vy = 0; app.stage.addChild(cat); //Start the game loop app.ticker.add(delta => gameLoop(delta)); } function gameLoop(delta){ //Update the cat's velocity cat.vx = 1; cat.vy = 1; //Apply the velocity values to the cat's //position to make it move cat.x += cat.vx; cat.y += cat.vy; } 

當你運行這段代碼,貓會每幀向右下方移動一個像素::

Moving sprites

若是你想讓貓往不一樣的方向移動怎麼辦?能夠把它的 vx 賦值爲 -1讓貓向左移動。能夠把它的 vy 賦值爲 -1讓貓向上移動。爲了讓貓移動的更快一點,把值設的更大一點,像3, 5, -2, 或者 -4。

你會在前面看到如何經過利用vxvy的速度值來模塊化精靈的速度,它對遊戲的鍵盤和鼠標控制系統頗有幫助,並且更容易實現物理模擬。

遊戲狀態

做爲一種代碼風格,也爲了幫你模塊你的代碼,我推薦在遊戲循環裏像這樣組織你的代碼:

//Set the game state state = play; //Start the game loop app.ticker.add(delta => gameLoop(delta)); function gameLoop(delta){ //Update the current game state: state(delta); } function play(delta) { //Move the cat 1 pixel to the right each frame cat.vx = 1 cat.x += cat.vx; }

你會看到gameLoop每秒60次調用了state函數。state函數是什麼呢?它被賦值爲 play。意味着play函數會每秒運行60次。

下面的代碼告訴你如何用這個新模式來重構上一個例子的代碼:

//Define any variables that are used in more than one function let cat, state; function setup() { //Create the `cat` sprite cat = new Sprite(resources["images/cat.png"].texture); cat.y = 96; cat.vx = 0; cat.vy = 0; app.stage.addChild(cat); //Set the game state state = play; //Start the game loop app.ticker.add(delta => gameLoop(delta)); } function gameLoop(delta){ //Update the current game state: state(delta); } function play(delta) { //Move the cat 1 pixel to the right each frame cat.vx = 1 cat.x += cat.vx; }

是的,我知道這有點兒 head-swirler! 可是,不要懼怕,花幾分鐘在腦海中想一遍這些函數是如何聯繫在一塊兒的。正如你將在下面看到的,結構化你的遊戲循環代碼,會使得切換遊戲場景和關卡這種操做變得更簡單。

鍵盤移動

只需再作一點微小的工做,你就能夠創建一個經過鼠標控制精靈移動的簡單系統。爲了簡化你的代碼,我建議你用一個名爲keyboard的自定義函數來監聽和捕捉鍵盤事件。

function keyboard(keyCode) { let key = {}; key.code = keyCode; key.isDown = false; key.isUp = true; key.press = undefined; key.release = undefined; //The `downHandler` key.downHandler = event => { if (event.keyCode === key.code) { if (key.isUp && key.press) key.press(); key.isDown = true; key.isUp = false; } event.preventDefault(); }; //The `upHandler` key.upHandler = event => { if (event.keyCode === key.code) { if (key.isDown && key.release) key.release(); key.isDown = false; key.isUp = true; } event.preventDefault(); }; //Attach event listeners window.addEventListener( "keydown", key.downHandler.bind(key), false ); window.addEventListener( "keyup", key.upHandler.bind(key), false ); return key; }

keyboard函數用起來很容易,能夠像這樣建立一個新的鍵盤對象:

let keyObject = keyboard(asciiKeyCodeNumber);

這個函數只接受一個參數就是鍵盤對應的ASCII鍵值數,也就是你想監聽的鍵盤按鍵。 這是鍵盤鍵ASCII值列表.

而後給鍵盤對象賦值pressrelease方法:

keyObject.press = () => { //key object pressed }; keyObject.release = () => { //key object released };

鍵盤對象也有 isDown 和 isUp 的布爾值屬性,你能夠用它們來檢查每一個按鍵的狀態。

examples文件夾裏看一下keyboardMovement.html文件是怎麼用keyboard函數的,利用鍵盤的方向鍵去控制精靈圖。運行它,而後用上下左右按鍵去讓貓在舞臺上移動。

Keyboard movement

這裏是代碼:

//Define any variables that are used in more than one function let cat, state; function setup() { //Create the `cat` sprite cat = new Sprite(resources["images/cat.png"].texture); cat.y = 96; cat.vx = 0; cat.vy = 0; app.stage.addChild(cat); //Capture the keyboard arrow keys let left = keyboard(37), up = keyboard(38), right = keyboard(39), down = keyboard(40); //Left arrow key `press` method left.press = () => { //Change the cat's velocity when the key is pressed cat.vx = -5; cat.vy = 0; }; //Left arrow key `release` method left.release = () => { //If the left arrow has been released, and the right arrow isn't down, //and the cat isn't moving vertically: //Stop the cat if (!right.isDown && cat.vy === 0) { cat.vx = 0; } }; //Up up.press = () => { cat.vy = -5; cat.vx = 0; }; up.release = () => { if (!down.isDown && cat.vx === 0) { cat.vy = 0; } }; //Right right.press = () => { cat.vx = 5; cat.vy = 0; }; right.release = () => { if (!left.isDown && cat.vy === 0) { cat.vx = 0; } }; //Down down.press = () => { cat.vy = 5; cat.vx = 0; }; down.release = () => { if (!up.isDown && cat.vx === 0) { cat.vy = 0; } }; //Set the game state state = play; //Start the game loop app.ticker.add(delta => gameLoop(delta)); } function gameLoop(delta){ //Update the current game state: state(delta); } function play(delta) { //Use the cat's velocity to make it move cat.x += cat.vx; cat.y += cat.vy }

給精靈分組

分組讓你可以讓你建立遊戲場景,而且像一個單一單元那樣管理類似的精靈圖。Pixi有一個對象叫 Container,它能夠幫你作這些工做。讓咱們弄清楚它是怎麼工做的。

想象一下你想展現三個精靈:一隻貓,一隻刺蝟和一隻老虎。建立它們,而後設置它們的位置 - 可是不要把它們添加到舞臺上

//The cat let cat = new Sprite(id["cat.png"]); cat.position.set(16, 16); //The hedgehog let hedgehog = new Sprite(id["hedgehog.png"]); hedgehog.position.set(32, 32); //The tiger let tiger = new Sprite(id["tiger.png"]); tiger.position.set(64, 64);

讓後建立一個animals容器像這樣去把他們聚合在一塊兒:

let animals = new Container();

而後用 addChild 去把精靈圖 添加到分組中 。

animals.addChild(cat); animals.addChild(hedgehog); animals.addChild(tiger);

最後把分組添加到舞臺上。

app.stage.addChild(animals);

(你知道的,stage對象也是一個Container。它是全部Pixi精靈的根容器。)

這就是上面代碼的效果:

Grouping sprites

你是看不到這個包含精靈圖的animals分組的。它僅僅是個容器而已。

Grouping sprites

不過你如今能夠像對待一個單一單元同樣對待animals分組。你能夠把Container看成是一個特殊類型的不包含任何紋理的精靈。

若是你須要獲取animals包含的全部子精靈,你能夠用它的children數組獲取。

console.log(animals.children)
//Displays: Array [Object, Object, Object]

這告訴你animals有三個子精靈。

由於animals分組跟其餘精靈同樣,你能夠改變它的xy的值,alphascale和其餘精靈的屬性。全部你改變了的父容器的屬性值,都會改變它的子精靈的相應屬性。因此若是你設置分組的xy的位置,全部的子精靈都會相對於分組的左上角從新定位。若是你設置了 animalsxy的位置爲64會發生什麼呢?

animals.position.set(64, 64);

整個分組的精靈都會向右和向下移動64像素。

Grouping sprites

animals分組也有它本身的尺寸,它是以包含的精靈所佔的區域計算出來的。你能夠像這樣來獲取widthheight的值:

console.log(animals.width); //Displays: 112 console.log(animals.height); //Displays: 112 

Group width and height

若是你改變了分組的寬和高會發生什麼呢?

animals.width = 200; animals.height = 200;

全部的孩子精靈都會縮放到剛纔你設定的那個值。

Group width and height

若是你喜歡,你能夠在一個 Container 裏嵌套許多其餘Container,若是你須要,徹底能夠建立一個更深的層次。然而,一個 DisplayObject (像 Sprite 或者其餘 Container)只能一次屬於一個父級。若是你用 addChild 讓一個精靈成爲其餘精靈的孩子。Pixi會自動移除它當前的父級,這是一個不用你操心的有用的管理方式。

 

局部位置和全局位置

當你往一個Container添加一個精靈時,它的xy的位置是 相對於分組的左上角 的。這是精靈的局部位置,舉個例子,你認爲這個貓在這張圖的哪一個位置?

Grouping sprites

讓咱們看看:

console.log(cat.x);
//Displays: 16

16?是的!這由於貓的只往分組的左上角偏移了16個像素。16是貓的局部位置。

精靈圖還有 全局位置 。全局位置是舞臺左上角到精靈錨點(一般是精靈的左上角)的距離。你能夠經過toGlobal方法的幫助找到精靈圖的全局位置:

parentSprite.toGlobal(childSprite.position)

這意味着你能在animals分組裏找到貓的全局位置:

console.log(animals.toGlobal(cat.position));
//Displays: Object {x: 80, y: 80...};

上面給你返回了xy的值爲80。這正是貓相對於舞臺左上角的相對位置,也就是全局位置。

若是你想知道一個精靈的全局位置,可是不知道精靈的父容器怎麼辦?每一個精靈圖有一個屬性叫parent 能告訴你精靈的父級是什麼。在上面的例子中,貓的父級是 animals。這意味着你能夠像以下代碼同樣獲得貓的全局位置:

cat.parent.toGlobal(cat.position);

即便你不知道貓的當前父級是誰,上面的代碼依然可以正確工做。

這還有一種方式可以計算出全局位置!並且,它實際上最好的方式,因此聽好啦!若是你想知道精靈到canvas左上角的距離,可是不知道或者不關心精靈的父親是誰,用getGlobalPosition方法。這裏展現如何用它來找到老虎的全局位置:

tiger.getGlobalPosition().x tiger.getGlobalPosition().y

它會給你返回xy的值爲128。 特別的是,getGlobalPosition是高精度的:當精靈的局部位置改變的同時,它會返回給你精確的全局位置。我曾要求Pixi開發團隊添加這個特殊的特性,以便於開發精確的碰撞檢測遊戲。(謝謝Matt和團隊真的把他加上去了!)

若是你想轉換全局位置爲局部位置怎麼辦?你能夠用toLocal方法。它的工做方式相似,可是一般是這種通用的格式:

sprite.toLocal(sprite.position, anyOtherSprite)

用 toLocal 找到一個精靈和其餘任何一個精靈之間的距離。這段代碼告訴你如何獲取老虎的相對於貓頭鷹的局部位置。

tiger.toLocal(tiger.position, hedgehog).x tiger.toLocal(tiger.position, hedgehog).y

上面的代碼會返回給你一個32的x值和一個32的y值。你能夠在例子中看到老虎的左上角和貓頭鷹的左上角距離32像素。

 

使用 ParticleContainer 分組精靈

Pixi有一個額外的,高性能的方式去分組精靈的方法稱做:ParticleContainerPIXI.ParticleContainer)。任何在ParticleContainer 裏的精靈都會比在一個普通的Container的渲染速度快2到5倍。這是用於提高遊戲性能的一個很棒的方法。

能夠像這樣建立 ParticleContainer :

let superFastSprites = new PIXI.particles.ParticleContainer();

而後用 addChild 去往裏添加精靈,就像往普通的 Container添加同樣。

若是你決定用ParticleContainer你必須作出一些妥協。在 ParticleContainer 裏的精靈圖只有一小部分基本屬性:xywidthheightscalepivotalphavisible - 就這麼多。並且,它包含的精靈不能再繼續嵌套本身的孩子精靈。 ParticleContainer 也不能用Pixi的先進的視覺效果像過濾器和混合模式。每一個ParticleContainer 只能用一個紋理(因此若是你想讓精靈有不一樣的表現方式你將不得不更換雪碧圖)。可是爲了獲得巨大的性能提高,這些妥協一般是值得的。你能夠在同一個項目中同時用 Container 和 ParticleContainer,而後微調一下你本身的優化。

爲何在 Particle Container 的精靈圖這麼快呢?由於精靈的位置是直接在GPU上計算的。Pixi開發團隊正在努力讓儘量多的雪碧圖在GPU上處理,因此頗有可能你用的最新版的Pixi的 ParticleContainer 的特性必定比我如今在這兒描述的特性多得多。查看當前 ParticleContainer 文檔以獲取更多信息。

當你建立一個 ParticleContainer,有四個參數能夠傳遞, sizepropertiesbatchSize 和 autoResize

let superFastSprites = new ParticleContainer(maxSize, properties, batchSize, autoResize);

默認的maxSize是 1,500。因此,若是你須要包裹更多的精靈,把它設置爲更高的數字。配置參數是一個擁有五個布爾值的對象:scalepositionrotationuvs 和 alpha。默認的值是 position 爲 true,其餘都爲 false。這意味着若是你想在 ParticleContainer改變精靈的rotationscalealpha, 或者 uvs,你得先把這些屬性設置爲 true,像這樣:

let superFastSprites = new ParticleContainer( size, { rotation: true, alphaAndtint: true, scale: true, uvs: true } );

可是,若是你感受你不須要用這些屬性,就保持它們爲 false 以實現出更好的性能。

uvs 是什麼呢?只有當它們在動畫時須要改變它們紋理子圖像的時候你須要設置它爲 true 。(想讓它工做,全部的精靈紋理須要在同一張雪碧圖上。)

(注意:UV mapping 是一個3D圖表展現術語,它指紋理(圖片)準備映射到三維表面的xy的座標。U 是 x 軸, V是 y 軸。WebGL用 xy 和 z 來進行三維空間定位,因此 U 和 V 被選爲表示2D圖片紋理的 x 和 y 。)

(我真不知道最後兩個參數幹什麼用的,就是batchSize 和 autoResize,若是你知道,就趕忙提個Issue吧!)

用Pixi繪製幾何圖形

使用圖片紋理是製做精靈最有效的方式之一,可是Pixi也提供了本身低級的繪畫工具。你可使用它們來創造矩形、線段、複雜的多邊形以及文本。而且它使用和Canvas Drawing API幾乎一致的api,因此若是你熟悉canvas的話,那麼幾乎沒有什麼新東西須要學習。固然另外一個巨大的優點在於,不一樣於Canvas的繪畫api,你使用Pixi繪製的圖形是經過WebGL在GPU上渲染的。Pixi可以讓你得到全部未觸碰到的性能。讓咱們簡單看一下如何創造一些基本圖形。下面是咱們將要使用前面代碼來創造的圖形。

Graphic primitives

 

矩形

全部的形狀的初始化都是先創造一個Pixi的Graphics的類 (PIXI.Graphics)的實例。

let rectangle = new Graphics();

調用beginFill和一個16進制的顏色值來設置矩形的填充顏色。下面展現如何設置顏色爲淡藍色。

rectangle.beginFill(0x66CCFF);

若是你想要給圖形設置一個輪廓,使用lineStyle方法。下面展現如何給矩形設置一個4像素寬alpha值爲1的紅色輪廓

rectangle.lineStyle(4, 0xFF3300, 1);

調用drawRect方法來畫一個矩形。它的四個參數是xywidth 和 height

rectangle.drawRect(x, y, width, height);

調用endFill結束繪製。

rectangle.endFill();

它看起來就像Canvas的繪畫api同樣!下面是繪製一個矩形涉及到的全部代碼,調整它的位置而且把它添加到舞臺吧。

let rectangle = new Graphics(); rectangle.lineStyle(4, 0xFF3300, 1); rectangle.beginFill(0x66CCFF); rectangle.drawRect(0, 0, 64, 64); rectangle.endFill(); rectangle.x = 170; rectangle.y = 170; app.stage.addChild(rectangle); 

這些代碼能夠在(170,170)這個位置創造一個寬高都爲64的藍色的紅框矩形。

圓形

調用drawCircle方法來創造一個圓。它的三個參數是xy 和 radius

drawCircle(x, y, radius)

不一樣於矩形和精靈,一個圓形的x和y座標也是它自身的圓點。下面展現如何創造半徑32像素的紫色圓形。

let circle = new Graphics(); circle.beginFill(0x9966FF); circle.drawCircle(0, 0, 32); circle.endFill(); circle.x = 64; circle.y = 130; app.stage.addChild(circle);

 

橢圓

drawEllipse是一個卓越的Canvas繪畫api,Pixi也可以讓你調用drawEllipse來繪製橢圓。

drawEllipse(x, y, width, height);

x/y座標位置決定了橢圓的左上角(想象橢圓被一個不可見的矩形邊界盒包圍着-盒的左上角表明了橢圓x/y的錨點位置)。下面是50像素寬20像素高的黃色橢圓。

let ellipse = new Graphics(); ellipse.beginFill(0xFFFF00); ellipse.drawEllipse(0, 0, 50, 20); ellipse.endFill(); ellipse.x = 180; ellipse.y = 130; app.stage.addChild(ellipse);

 

圓角矩形

Pixi一樣容許你調用drawRoundedRect方法來建立圓角矩形。這個方法的最後一個參數cornerRadius是單位爲像素的數字,它表明矩形的圓角應該有多圓。

drawRoundedRect(x, y, width, height, cornerRadius)

下面展現如何建立一個圓角半徑爲10的圓角矩形。

let roundBox = new Graphics(); roundBox.lineStyle(4, 0x99CCFF, 1); roundBox.beginFill(0xFF9933); roundBox.drawRoundedRect(0, 0, 84, 36, 10) roundBox.endFill(); roundBox.x = 48; roundBox.y = 190; app.stage.addChild(roundBox);

 

線段

想必你已經看過上面定義線段的lineStyle方法了。你能夠調用moveTo 和 lineTo方法來畫線段的起點和終點,就和Canvas繪畫api中的同樣。下面展現如何繪製一條4像素寬的白色對角線。

let line = new Graphics(); line.lineStyle(4, 0xFFFFFF, 1); line.moveTo(0, 0); line.lineTo(80, 50); line.x = 32; line.y = 32; app.stage.addChild(line);

PIXI.Graphics對象,好比線段,都有x 和 y值,就像精靈同樣,因此你能夠在繪製完它們以後將他們定位到舞臺的任意位置。

 

多邊形

你可使用drawPolygon方法來將線段鏈接起來而且填充顏色來創造複雜圖形。drawPolygon的參數是一個路徑數組,數組中的值爲決定圖形上每一個點位置的x/y座標。

let path = [ point1X, point1Y, point2X, point2Y, point3X, point3Y ]; graphicsObject.drawPolygon(path);

drawPolygon會將上面三個點鏈接起來創造圖形。下面是如何使用drawPolygon來鏈接三條線從而建立一個紅底藍邊的三角形。咱們將三角形繪製在(0,0)的位置上,以後經過調整它的x 和 y屬性來移動它在舞臺上的位置。

let triangle = new Graphics(); triangle.beginFill(0x66FF33); //Use `drawPolygon` to define the triangle as //a path array of x/y positions triangle.drawPolygon([ -32, 64, //First point 32, 64, //Second point 0, 0 //Third point ]); //Fill shape's color triangle.endFill(); //Position the triangle after you've drawn it. //The triangle's x/y position is anchored to its first point in the path triangle.x = 180; triangle.y = 22; app.stage.addChild(triangle);

 

顯示文本

使用一個 Text 對象 (PIXI.Text)在舞臺上展現文本。簡單來講,你能夠這樣使用它:

let message = new Text("Hello Pixi!"); app.stage.addChild(message);

這將會在畫布上展現文本「Hello, Pixi」。Pixi的文本對象繼承自Sprite類,因此它包含了全部相同的屬性,像xywidthheightalpha, 和 rotation。你能夠像處理其餘精靈同樣在舞臺上定位或調整文本。例如,你能夠像下面這樣使用position.set來設置messagexy位置:

message.position.set(54, 96);

Displaying text

這樣你會獲得基礎的未加修飾的文本。可是若是你想要更絢麗的文字,使用Pixi的TextStyle函數來自定義文字效果。下面展現如何操做:

let style = new TextStyle({ fontFamily: "Arial", fontSize: 36, fill: "white", stroke: '#ff3300', strokeThickness: 4, dropShadow: true, dropShadowColor: "#000000", dropShadowBlur: 4, dropShadowAngle: Math.PI / 6, dropShadowDistance: 6, });

這將建立一個新的包含全部你想用的樣式的style對象。全部樣式屬性,see here

添加style對象做爲Text函數的第二個參數來應用樣式到文本上,就像這樣:

let message = new Text("Hello Pixi!", style);

Displaying text

若是你想要在你建立文本對象以後改變它的內容,使用text屬性。

message.text = "Text changed!";

若是你想要從新定義樣式屬性,使用style屬性。

message.style = {fill: "black", font: "16px PetMe64"};

Pixi經過調用Canvas繪畫api將文本渲染成不可見或臨時的canvas元素來建立文本對象。它以後會將畫布轉化爲WebGL紋理,因此能夠被映射到精靈上。這就是爲何文本的顏色須要被包裹成字符串:那是Canvas繪畫api的顏色值。與任何canvas顏色值同樣,你可使用「red」或「green」等經常使用顏色的單詞,或使用rgba,hsla或十六進制值。

Pixi也能包裹文本的長段。設置文本的 wordWrap 樣式屬性到 true,而後設置wordWrapWidth到一行文字應該到的最大像素。調用align屬性來設置多行文本的對齊方式。

message.style = {wordWrap: true, wordWrapWidth: 100, align: center};

(注意:align 不會影響單行文本。)

若是你想要使用自定義的字體文件,使用CSS的@font-face規則來連接字體文件到Pixi應用運行的HTML頁面。

@font-face {
  font-family: "fontFamilyName"; src: url("fonts/fontFile.ttf"); }

添加這個@font-face語句到你的HTML頁面的CSS裏面。

Pixi也支持位圖字體。你可使用Pixi的加載器來加載XML位圖文件,就像你加載JSON或圖片文件同樣。

碰撞檢測

如今你知道了如何製造種類繁多的圖形對象,可是你能用他們作什麼?一個有趣的事情是利用它製做一個簡單的 碰撞檢測系統。你能夠用一個叫作:hitTestRectangle 的自定義的函數來檢測兩個矩形精靈是否接觸。

hitTestRectangle(spriteOne, spriteTwo)

若是它們重疊, hitTestRectangle 會返回 true。你能夠用 hitTestRectangle 結合 if 條件語句去檢測兩個精靈是否碰撞:

if (hitTestRectangle(cat, box)) { //There's a collision } else { //There's no collision }

正如你所見, hitTestRectangle 是走入遊戲設計這片宇宙的大門。

運行在 examples 文件夾的 collisionDetection.html 文件,看看怎麼用 hitTestRectangle工做。用方向按鍵去移動貓,若是貓碰到了盒子,盒子會變成紅色,而後 "Hit!" 文字對象會顯示出來。

Displaying text

你已經看到了建立這些全部元素的代碼,讓貓移動的鍵盤控制。惟一的新的東西就是 hitTestRectangle 函數被用在 play 函數裏檢測碰撞。

function play(delta) { //use the cat's velocity to make it move cat.x += cat.vx; cat.y += cat.vy; //check for a collision between the cat and the box if (hitTestRectangle(cat, box)) { //if there's a collision, change the message text //and tint the box red message.text = "hit!"; box.tint = 0xff3300; } else { //if there's no collision, reset the message //text and the box's color message.text = "No collision..."; box.tint = 0xccff99; } }

play 函數被每秒調用了60次,每一次這個 if 條件語句都會在貓和盒子之間進行碰撞檢測。若是 hitTestRectangle 爲 true,那麼文字 message 對象會用 setText 方法去顯示 "Hit":

message.text = "Hit!";

這個盒子的顏色改變的效果是把盒子的 tint 屬性改爲一個16進制的紅色的值實現的。

box.tint = 0xff3300;

若是沒有碰撞,消息和盒子會保持它們的原始狀態。

message.text = "No collision..."; box.tint = 0xccff99;

代碼很簡單,可是你已經創造了一個看起來徹底活着的互動的世界!它簡直跟魔術同樣!使人驚訝的是,你大概已經擁有了你須要用Pixi製做遊戲的所有技能!

 

碰撞檢測函數

hitTestRectangle 函數都有些什麼呢?它作了什麼,還有它是如何工做的?關於碰撞檢測算法的細節有些超出了本教程的範圍。最重要的事情是你要知道如何使用它。可是,只是做爲你的參考資料,不讓你好奇,這裏有所有的 hitTestRectangle 函數的定義。你能從註釋弄明白它都作了什麼嗎?

function hitTestRectangle(r1, r2) { //Define the variables we'll need to calculate let hit, combinedHalfWidths, combinedHalfHeights, vx, vy; //hit will determine whether there's a collision hit = false; //Find the center points of each sprite r1.centerX = r1.x + r1.width / 2; r1.centerY = r1.y + r1.height / 2; r2.centerX = r2.x + r2.width / 2; r2.centerY = r2.y + r2.height / 2; //Find the half-widths and half-heights of each sprite r1.halfWidth = r1.width / 2; r1.halfHeight = r1.height / 2; r2.halfWidth = r2.width / 2; r2.halfHeight = r2.height / 2; //Calculate the distance vector between the sprites vx = r1.centerX - r2.centerX; vy = r1.centerY - r2.centerY; //Figure out the combined half-widths and half-heights combinedHalfWidths = r1.halfWidth + r2.halfWidth; combinedHalfHeights = r1.halfHeight + r2.halfHeight; //Check for a collision on the x axis if (Math.abs(vx) < combinedHalfWidths) { //A collision might be occuring. Check for a collision on the y axis if (Math.abs(vy) < combinedHalfHeights) { //There's definitely a collision happening hit = true; } else { //There's no collision on the y axis hit = false; } } else { //There's no collision on the x axis hit = false; } //`hit` will be either `true` or `false` return hit; }; 

實例學習: 寶物獵人

我要告訴你你如今已經擁有了所有的技能去開始製做一款遊戲。什麼?你不相信我?讓我爲你證實它!讓咱們來作一個簡單的對象收集和躲避的敵人的遊戲叫:寶藏獵人。(你能在examples文件夾中找到它。)

Treasure Hunter

寶藏獵手是一個簡單的完整的遊戲的例子,它能讓你把目前所學的全部工具都用上。用鍵盤的方向鍵能夠幫助探險者找到寶藏並帶它出去。六隻怪物在地牢的地板上上下移動,若是它們碰到了探險者,探險者變爲半透明,並且他右上角的血條會減小。若是全部的血都用完了,"You Lost!"會出如今舞臺上;若是探險者帶着寶藏到達了出口,顯示 「You Won!」 。儘管它是一個基礎的原型,寶藏獵手包含了不少大型遊戲裏很大一部分元素:紋理貼圖集,人機交互,碰撞以及多個遊戲場景。讓咱們一塊兒去看看遊戲是如何被它們組合起來的,以便於你能夠用它做你本身開發的遊戲的起點。

代碼結構

打開 treasureHunter.html 文件,你將會看到全部的代碼都在一個大的文件裏。下面是一個關於如何組織全部代碼的概覽:

//Setup Pixi and load the texture atlas files - call the `setup` //function when they've loaded function setup() { //Initialize the game sprites, set the game `state` to `play` //and start the 'gameLoop' } function gameLoop(delta) { //Runs the current game `state` in a loop and renders the sprites } function play(delta) { //All the game logic goes here } function end() { //All the code that should run at the end of the game } //The game's helper functions: //`keyboard`, `hitTestRectangle`, `contain` and `randomInt`

把這個看成你遊戲代碼的藍圖,讓咱們看看每一部分是如何工做的。

 

用 setup 函數初始化遊戲

一旦紋理圖集圖片被加載進來了,setup函數就會執行。它只會執行一次,可讓你爲遊戲執行一次安裝任務。這是一個用來建立和初始化對象、精靈、遊戲場景、填充數據數組或解析加載JSON遊戲數據的好地方。

這是寶藏獵手setup函數的縮略圖和它要執行的任務。

function setup() { //Create the `gameScene` group //Create the `door` sprite //Create the `player` sprite //Create the `treasure` sprite //Make the enemies //Create the health bar //Add some text for the game over message //Create a `gameOverScene` group //Assign the player's keyboard controllers //set the game state to `play` state = play; //Start the game loop app.ticker.add(delta => gameLoop(delta)); } 

最後兩行代碼,state = play;gameLoop()多是最重要的。運行 gameLoop 切換了遊戲的引擎,並且引起了 play 一直被循環調用。可是在咱們看它如何工做以前,讓咱們看看 setup 函數裏的代碼都作了什麼。

 

建立遊戲場景

setup 函數建立了兩個被稱爲gameScene 和 gameOverScene的 Container 分組。他們都被添加到了舞臺上。

gameScene = new Container(); app.stage.addChild(gameScene); gameOverScene = new Container(); app.stage.addChild(gameOverScene); 

全部的的遊戲主要部分的精靈都被添加到了gameScene分組。遊戲結束的文字在遊戲結束後顯示,應當被添加到gameOverScene分組。

Displaying text

儘管它是在 setup 函數中添加的,可是 gameOverScene不該在遊戲一開始的時候顯示,因此它的 visible 屬性被初始化爲 false

gameOverScene.visible = false;

你會在後面看到,爲了在遊戲結束以後顯示文字,當遊戲結束gameOverScene 的 visible 屬性會被設置爲 true 。

 

製做地牢,門,獵人和寶藏

玩家、出口、寶箱和地牢背景圖都是從紋理圖集製做而來的精靈。有一點很重要的是,他們都是被當作 gameScene 的孩子添加進來的。

//Create an alias for the texture atlas frame ids id = resources["images/treasureHunter.json"].textures; //Dungeon dungeon = new Sprite(id["dungeon.png"]); gameScene.addChild(dungeon); //Door door = new Sprite(id["door.png"]); door.position.set(32, 0); gameScene.addChild(door); //Explorer explorer = new Sprite(id["explorer.png"]); explorer.x = 68; explorer.y = gameScene.height / 2 - explorer.height / 2; explorer.vx = 0; explorer.vy = 0; gameScene.addChild(explorer); //Treasure treasure = new Sprite(id["treasure.png"]); treasure.x = gameScene.width - treasure.width - 48; treasure.y = gameScene.height / 2 - treasure.height / 2; gameScene.addChild(treasure);

把它們都放在 gameScene 分組會使咱們在遊戲結束的時候去隱藏 gameScene 和顯示 gameOverScene 操做起來更簡單。

 

製造泡泡怪們

六個泡泡怪是被循環建立的。每個泡泡怪都被賦予了一個隨機的初始位置和速度。每一個泡泡怪的垂直速度都被交替的乘以 1或者 -1 ,這就是每一個怪物和相鄰的下一個怪物運動的方向都是相反的緣由,每一個被建立的怪物都被放進了一個名爲 blobs的數組。

let numberOfBlobs = 6, spacing = 48, xOffset = 150, speed = 2, direction = 1; //An array to store all the blob monsters blobs = []; //Make as many blobs as there are `numberOfBlobs` for (let i = 0; i < numberOfBlobs; i++) { //Make a blob let blob = new Sprite(id["blob.png"]); //Space each blob horizontally according to the `spacing` value. //`xOffset` determines the point from the left of the screen //at which the first blob should be added let x = spacing * i + xOffset; //Give the blob a random `y` position let y = randomInt(0, stage.height - blob.height); //Set the blob's position blob.x = x; blob.y = y; //Set the blob's vertical velocity. `direction` will be either `1` or //`-1`. `1` means the enemy will move down and `-1` means the blob will //move up. Multiplying `direction` by `speed` determines the blob's //vertical direction blob.vy = speed * direction; //Reverse the direction for the next blob direction *= -1; //Push the blob into the `blobs` array blobs.push(blob); //Add the blob to the `gameScene` gameScene.addChild(blob); } 

 

製做血條

當你玩兒寶藏獵人的時候,你會發現當獵人碰到其中一個敵人時,場景右上角的血條寬度會減小。這個血條是如何被製做的?他就是兩個相同的位置的重疊的矩形:一個黑色的矩形在下面,紅色的上面。他們被分組到了一個單獨的 healthBar 分組。 healthBar 而後被添加到 gameScene 並在舞臺上被定位。

//Create the health bar healthBar = new PIXI.DisplayObjectContainer(); healthBar.position.set(stage.width - 170, 4) gameScene.addChild(healthBar); //Create the black background rectangle let innerBar = new PIXI.Graphics(); innerBar.beginFill(0x000000); innerBar.drawRect(0, 0, 128, 8); innerBar.endFill(); healthBar.addChild(innerBar); //Create the front red rectangle let outerBar = new PIXI.Graphics(); outerBar.beginFill(0xFF3300); outerBar.drawRect(0, 0, 128, 8); outerBar.endFill(); healthBar.addChild(outerBar); healthBar.outer = outerBar;

你會看到 healthBar 添加了一個名爲 outer 的屬性。它僅僅是引用了 outerBar (紅色的矩形)以便於過會兒可以被很方便的獲取。

healthBar.outer = outerBar;

你能夠不這麼作,可是爲何不呢?這意味若是你想控制紅色 outerBar 的寬度,你能夠像這樣順暢的寫以下代碼:

healthBar.outer.width = 30;

這樣的代碼至關整齊並且可讀性強,因此咱們會一直保留它!

 

製做消息文字

當遊戲結束的時候, 「You won!」 或者 「You lost!」 的文字會顯示出來。這使用文字紋理製做的,並添加到了 gameOverScene。由於 gameOverScene 的 visible 屬性設爲了 false ,當遊戲開始的時候,你看不到這些文字。這段代碼來自 setup 函數,它建立了消息文字,並且被添加到了 gameOverScene

let style = new TextStyle({ fontFamily: "Futura", fontSize: 64, fill: "white" }); message = new Text("The End!", style); message.x = 120; message.y = app.stage.height / 2 - 32; gameOverScene.addChild(message);

 

開始遊戲

全部的讓精靈移動的遊戲邏輯代碼都在 play 函數裏,這是一個被循環執行的函數。這裏是 play 函數都作了什麼的整體概覽:

function play(delta) { //Move the explorer and contain it inside the dungeon //Move the blob monsters //Check for a collision between the blobs and the explorer //Check for a collision between the explorer and the treasure //Check for a collision between the treasure and the door //Decide whether the game has been won or lost //Change the game `state` to `end` when the game is finsihed }

讓咱們弄清楚這些特性都是怎麼工做的吧。

 

移動探險者

探險者是被鍵盤控制的,實現它的代碼跟你在以前學習的鍵盤控制代碼很類似。在 play 函數裏, keyboard 對象修改探險者的速度,這個速度和探險者的位置相加。

explorer.x += explorer.vx; explorer.y += explorer.vy;

 

控制運動的範圍

一個新的地方的是,探險者的運動是被包裹在地牢的牆體以內的。綠色的輪廓代表了探險者運動的邊界。

Displaying text

經過一個名爲 contain 的自定義函數能夠幫助實現。

contain(explorer, {x: 28, y: 10, width: 488, height: 480});

contain 接收兩個參數。第一個是你想控制的精靈。第二個是包含了 xywidth 和height屬性的任何一個對象。在這個例子中,控制對象定義了一個區域,它稍微比舞臺小了一點,和地牢的尺寸同樣。

這裏是實現了上述功能的 contain 函數。函數檢查了精靈是否跨越了控制對象的邊界。若是超出,代碼會把精靈繼續放在那個邊界上。 contain 函數也返回了一個值可能爲"top", "right", "bottom" 或者 "left" 的 collision 變量,取決於精靈碰到了哪個邊界。(若是精靈沒有碰到任何邊界,collision 將返回 undefined 。)

function contain(sprite, container) { let collision = undefined; //Left if (sprite.x < container.x) { sprite.x = container.x; collision = "left"; } //Top if (sprite.y < container.y) { sprite.y = container.y; collision = "top"; } //Right if (sprite.x + sprite.width > container.width) { sprite.x = container.width - sprite.width; collision = "right"; } //Bottom if (sprite.y + sprite.height > container.height) { sprite.y = container.height - sprite.height; collision = "bottom"; } //Return the `collision` value return collision; }

你會在接下來看到 collision 的返回值在代碼裏是如何讓怪物在地牢的頂部和底部之間來回反彈的。

 

移動怪物

play 函數也可以移動怪物,保持它們在地牢的牆體以內,並檢測每一個怪物是否和玩家發生了碰撞。若是一隻怪物撞到了地牢的頂部或者底部的牆,它就會被設置爲反向運動。完成全部這些功能都是經過一個 forEach循環,它每一幀都會遍歷在 blobs 數組裏的每個怪物。

blobs.forEach(function(blob) { //Move the blob blob.y += blob.vy; //Check the blob's screen boundaries let blobHitsWall = contain(blob, {x: 28, y: 10, width: 488, height: 480}); //If the blob hits the top or bottom of the stage, reverse //its direction if (blobHitsWall === "top" || blobHitsWall === "bottom") { blob.vy *= -1; } //Test for a collision. If any of the enemies are touching //the explorer, set `explorerHit` to `true` if(hitTestRectangle(explorer, blob)) { explorerHit = true; } }); 

你能夠在上面這段代碼中看到, contain 函數的返回值是如何被用來讓怪物在牆體之間來回反彈的。一個名爲 blobHitsWall的變量被用來捕獲返回值:

let blobHitsWall = contain(blob, {x: 28, y: 10, width: 488, height: 480});

blobHitsWall 一般應該是 undefined。可是若是怪物碰到了頂部的牆,blobHitsWall 將會變成 "top"。若是碰到了底部的牆,blobHitsWall 會變爲 "bottom"。若是它們其中任何一種狀況爲 true,你就能夠經過給怪物的速度取反來讓它反向運動。這是實現它的代碼:

if (blobHitsWall === "top" || blobHitsWall === "bottom") { blob.vy *= -1; }

把怪物的 vy (垂直速度)乘以 -1 就會反轉它的運動方向。

 

檢測碰撞

在上面的循環代碼裏用了 hitTestRectangle 來指明是否有敵人碰到了獵人。

if(hitTestRectangle(explorer, blob)) { explorerHit = true; }

若是 hitTestRectangle 返回 true,意味着發生了一次碰撞,名爲 explorerHit 的變量被設置爲了 true。若是 explorerHit爲 true, play 函數讓獵人變爲半透明,而後把 health 條減小1像素的寬度。

if(explorerHit) {

  //Make the explorer semi-transparent explorer.alpha = 0.5; //Reduce the width of the health bar's inner rectangle by 1 pixel healthBar.outer.width -= 1; } else { //Make the explorer fully opaque (non-transparent) if it hasn't been hit explorer.alpha = 1; } 

若是 explorerHit 是 false,獵人的 alpha 屬性將保持1,徹底不透明。

play 函數也要檢測寶箱和探險者之間的碰撞。若是發生了一次撞擊, treasure 會被設置爲探險者的位置,在作一點偏移。看起來像是獵人攜帶着寶藏同樣。

Displaying text

這段代碼實現了上述效果:

if (hitTestRectangle(explorer, treasure)) { treasure.x = explorer.x + 8; treasure.y = explorer.y + 8; }

 

處理到達出口和結束遊戲

遊戲結束有兩種方式:若是你攜帶寶藏到達出口你將贏得遊戲,或者你的血用完你就死了。

想要獲勝,寶箱只需碰到出口就好了。若是碰到了出口,遊戲的 state 會被設置爲 end, message 文字會顯示 "You won!"。

if (hitTestRectangle(treasure, door)) { state = end; message.text = "You won!"; }

若是你的血用完,你將輸掉遊戲。遊戲的 state 也會被設置爲 end, message 文字會顯示 "You Lost!"。

if (healthBar.outer.width < 0) { state = end; message.text = "You lost!"; }

可是這是什麼意思呢?

state = end;

你會在早些的例子看到 gameLoop 在持續的每秒60次的更新 state 函數。 gameLoop 的代碼以下:

function gameLoop(delta){ //Update the current game state: state(delta); }

你也會記住咱們給 state 設定的初始值爲 play,這也就是爲何 play 函數會循環執行。經過設置 state 爲 end 咱們告訴代碼咱們想循環執行另外一個名爲 end 的函數。在大一點的遊戲你可能會爲每個遊戲等級設置 tileScene 狀態和狀態集,像leveOnelevelTwo 和 levelThree

end 函數是什麼?就是它!

function end() { gameScene.visible = false; gameOverScene.visible = true; }

它僅僅是反轉了遊戲場景的顯示。這就是當遊戲結束的時候隱藏 gameScene 和顯示 gameOverScene 。

這是一個如何更換遊戲狀態的一個很簡單的例子,可是你能夠想在你的遊戲裏添加多少狀態就添加多少狀態,而後給它們添加你須要的代碼。而後改變 state 爲任何你想循環的函數。

這就是完成寶藏獵人所須要的一切了。而後再經過更多一點的工做就能把這個簡單的原型變成一個完整的遊戲 - 快去試試吧!

一些關於精靈的其餘知識

目前爲止你已經學會了如何用至關多有用的精靈的屬性,像xyvisible, 和 rotation ,它們讓你可以讓你很大程度上控制精靈的位置和外觀。可是Pixi精靈也有其餘不少有用的屬性可使用。 這是一個完整的列表

Pixi的類繼承體系是怎麼工做的呢?(什麼是 類 , 什麼是 繼承?點擊這個連接瞭解.)Pixi的精靈遵循如下原型鏈構建了一個繼承模型:

DisplayObject > Container > Sprite

繼承意味着在繼承鏈後面的類能夠用以前的類的屬性和方法。最基礎的類是 DisplayObject。任何只要是DisplayObject 均可以被渲染在舞臺上。 Container是繼承鏈的下一個類。它容許 DisplayObject做爲其餘 DisplayObject的容器。繼承鏈的第三個類是 Sprite 。這個類被你用來建立你遊戲的大部分對象。然而,不久前你就會學習瞭如何用 Container 去給精靈分組。

展望將來

Pixi能作不少事情,可是不能作所有的事情!若是你想用Pixi開始製做遊戲或者複雜的交互型應用,你可能會須要一些有用的庫:

  • Bump: 一個爲了遊戲準備的完整的2D碰撞函數集.
  • Tink: 拖放, 按鈕, 一個通用的指針和其餘有用的交互工具集。
  • Charm: 給Pixi精靈準備的簡單易用的緩動動畫效果。
  • Dust: 建立像爆炸,火焰和魔法等粒子效果。
  • Sprite Utilities: 建立和使用Pixi精靈的一個更容易和更直觀的作法,包括添加狀態機和動畫播放器。讓Pixi的工做變得更有趣。
  • Sound.js: 一個加載,控制和生成聲音和音樂效果的微型庫。包含了一切你須要添加到遊戲的聲音。
  • Smoothie: 使用真正的時間增量插值實現的超平滑精靈動畫。它也容許爲你的運行的遊戲和應用指定 fps (幀率) ,而且把你的精靈圖循環渲染徹底從你的應用邏輯循環中分離出去。

你能夠在這兒在這本書裏找到如何用結合Pixi使用這些庫。 學習PixiJS.

 

Hexi

若是你想使用所有的這些功能庫,但又不想給本身整一堆麻煩?用 Hexi:建立遊戲和交互應用的完整開發環境:

https://github.com/kittykatattack/hexi

它把最新版本的Pixi(最新的 穩定 的一個)和這些庫(還有更多!)打包在了一塊兒,爲了能夠經過一種簡單並且有趣的方式去建立遊戲。Hexi 也容許你直接獲取 PIXI 對象,因此你可直接寫底層的Pixi代碼,而後任意的選擇你須要的Hexi額外的方便的功能。

 

BabylonJS

Pixi能夠很好地完成2D交互式媒體,可是對於3D卻無能爲力。當你準備踏進3D領域,這個最有潛力的領域的時候,不妨使用這個爲WEB遊戲開發者準備的用起來很是簡單的3D庫:BabylonJS。它是提高你技能的下一步。

支持這個工程

買這本書吧!不敢相信,有人竟然會付錢讓我完成這個教程並把它寫成一本書!

學習 PixiJS

(它可不是一本毫無價值的「電子書」,而是一本真實的,很厚的紙質書!由世界上最大的出版商,施普林格出版!這意味着你能夠邀請你的朋友過來,放火,烤棉花糖!!)它比本教程多出了80%的內容,它包含了全部如何用Pixi製做全部交互應用和遊戲的必要技術。)

你能夠在裏面學到:

  • 製做動畫遊戲角色。
  • 建立一個全功能動畫狀態播放器。
  • 動態的動畫線條和形狀。
  • 用平鋪的精靈實現無限滾動視差。
  • 使用混合模式,濾鏡,調色,遮罩,視頻,和渲染紋理。
  • 爲多種分辨率生成內容。
  • 建立交互按鈕。
  • 爲Pixi建立一個靈活的拖動和拖放界面。
  • 建立粒子效果。
  • 創建一個能夠展到任何大小的穩定的軟件架構模型。
  • 製做一個完整的遊戲。

不只如此,做爲一個福利,全部的代碼徹底使用最新版本的 JavaScript:ES6/2015 編寫而成的。儘管這本書基於Pixi V3.x,可是它仍舊能在Pixi 4.x上很好的運行!

若是你想支持這個項目,請買一份本書,而後在買一份給你的媽媽!(譯者:??????)

或者直接來這裏慷慨的捐贈吧: http://www.msf.org

 

轉載參考

https://github.com/Zainking/learningPixi#introduction

相關文章
相關標籤/搜索