pixi(入門)


Pixi教程

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

這個教程將要一步步介紹怎么用Pixi做游戲或者交互式媒體。這個教程已經升級到 Pixi v4.5.5。如果你喜歡這個教程,你一定也喜歡這本書,它比這個教程多了80%的內容

目錄:

  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與其他有用的框架無縫集成。

Pixi的API事實上比起久經沙場又老舊的Macromedia/Adobe Flash API要精致。如果你是一個Flash開發者,將會對這樣的API感覺更好。其他的同類渲染框架(比如CreateJS,Starling, Sparrow 和 Apple’s SpriteKit.)也在使用類似的API。Pixi API的優勢在於它是通用的:它不是一個游戲引擎。這是一個優勢,因為它給了你所有的自由去做任何你想做的事,甚至用它可以寫成你自己的游戲引擎。(譯者:作者這點說的很對,譯者有一個朋友就使用它制作自己的Galgame引擎AVG.js)。

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

你在開始這個教程之前需要知道什么呢?

你需要一個對於HTML和JavaScript大致的了解。你沒必要成為這方面的專家才能開始,即使一個野心勃勃的初學者也可以開始學習。這本書就是一個學習的好地方:

Foundation Game Design with HTML5 and JavaScript

我知道這本書是最好的,因為這本書是我寫的!

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

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM