一.pixi.js簡介
pixi.js是一個非常快速的2D精靈渲染引擎。它可以幫助我們顯示,動畫和管理交互式圖形。如此一來,我們可以使用javascript和其它HTML5技術來輕鬆實現一個應用程序或者完成一款遊戲。它有一個語義化的、簡潔的API,包含了許多有用的功能。比如說支持紋理地圖集,也提供一個通過動畫精靈(交互式圖像)所構建的精簡的系統。它還爲我們提供了一個完整的場景圖,我們可以創建嵌套精靈的層次結構(也就是精靈當中嵌套精靈)。同樣的也允許我們將鼠標以及觸摸的事件添加到精靈上。而且,最重要的還是,pixi.js可以根據我們的實際需求來使用,很好的適應個人的編碼風格,並且還能夠和框架無縫集成。
pixi.js的API事實上是陳舊的Macromedia/Adobe Flash API的一個改進。熟練flash的開發人員將會有一種回到自己家裏一樣的熟悉。其它使用過類似API的精靈渲染框架有: CreateJS,Starling, Sparrow 和 Apple’s SpriteKit。pixi.js的優點在於它的通用性:它不是一個遊戲引擎。這是極好的,因爲它可以完全任由我們自由發揮,做自己的事情,甚至還可以用它寫一個自己的遊戲引擎。在學習pixi.js之前,我們需要先對HTML,CSS,Javascript有一些瞭解。因爲這會對我們學習pixi.js有很大的幫助。
二.pixi.js安裝
1.前往github安裝
2.使用npm安裝
也可以使用npm來安裝。首先需要安裝node.js。
當安裝node.js完成之後,會自動完成npm的安裝。然後就可以通過npm將pixi.js安裝在全局。命令如下:
npm install pixi.js -g
//也可以簡寫爲
npm i pixi.js -g
或者也可以將pixi.js安裝在當前項目的目錄之下。命令如下:
npm install pixi.js -D //或者
npm install pixi.js --save -dev
//也可以簡寫爲
npm i pixi.js -D
三.開始使用pixi.js
1.引入pixi.js
安裝pixi.js完成之後,我們就可以使用了。首先在你的項目目錄下創建一個基礎的.html文件。 然後在你html文件中,使用script標籤來,加載這個js文件。代碼如下:
<script src="/pixi.min.js"></script>
或者,你也可以使用CDN來引入這個js文件。如下:
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.1.3/pixi.min.js"></script>
<!--或者使用bootstrap的cdn來引入-->
<script src="https://cdn.bootcss.com/pixi.js/5.1.3/pixi.min.js"></script>
如果使用es6的模塊化加載pixi.js,那麼就需要注意了,因爲pixi.js沒有默認導出。所以正確的引入方式應該如下:
import * as PIXI from 'pixi.js'
2.一個簡單的示例
好了,接下來就是寫點js代碼,看看pixi.js是否在工作中。代碼如下:
const type = "WebGL";
if (!PIXI.utils.isWebGLSupported()) {
type = "canvas";
}
PIXI.utils.sayHello(type);
如果能在瀏覽器控制檯中,看到如下圖所示的標誌,那麼就證明pixi.js加載成功。
我們來分析一下以上的代碼的意思,先定義一個變量,值是字符串"webGL",然後有一個if語句判斷如果支持webGL,那麼就改變這個變量的值爲canvas。然後調用pixi.js所封裝的在控制檯打印這個值的方法,即sayHello方法。
3.創建你自己的pixi應用和舞臺
現在,我們可以愉快的開始使用pixi.js呢。首先,我們可以創建一個可以顯示圖片的矩形區域,pixi.js有一個Application對象來幫助我們創建它。它會自動創建一個canvas的HTML標籤。並且還能夠自動計算出讓你的圖片能夠在canvas元素中顯示。然後,你需要創建一個特殊的pixi容器對象,它被叫做舞臺。正如你所看到的,這個舞臺元素會被當做根容器,然後你可以在這個根容器使用pixi來顯示你想要顯示的東西。以下是你需要創建一個pixi應用對象以及舞臺對象所必要的代碼。
//創建一個pixi應用對象
let app = new PIXI.Application({width: 256, height: 256});
//將這個應用對象元素添加到dom文檔中
document.body.appendChild(app.view);
以上代碼運行在瀏覽器中,效果如圖所示:
很好,以上的代碼所代表的意思就是,我們在HTML DOM中創建了背景顏色爲黑色(默認顏色)的一個寬爲256,高爲256的canvas元素(單位默認是像素)。沒錯,就是一個黑色的矩形。我們可以看到PIXI.Application是一個構造函數,它會根據瀏覽器是支持canvas還是webGL來決定使用哪一個渲染圖像。函數裏面可以不傳參數,也可以傳一個對象當做參數。如果不傳參數,那麼會使用默認的參數。這個對象,我們可以稱之爲option對象。比如以上,我們就傳了width和height屬性。
4.屬性
當然,我們還可以使用更多的屬性,例如以下代碼:
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
});
這些屬性到底代表什麼意思呢?antialias屬性使得字體的邊界和圖形更加平滑(webGL的anti-aliasing在所有平臺都不可用,你需要在你的遊戲平臺中去做測試。)。transparent屬性是設置整個canvas元素的透明度。resolution屬性讓不同分辨率和不同像素的平臺運行起來更容易一些。只要將這個屬性的值設置爲1就可以應付大多數的工程項目呢。想要知道這個屬性的所有細節,可以查看這個項目Mat Grove'sexplanation的代碼。
pixi.js默認是通過WebGL來渲染的。因爲webGL的速度非常塊,並且我們還可以使用一些將要學習的壯觀的視覺效果。當然,如果需要強制性的使用Canvas來取代webGL渲染。我們可以將forceCanvas的值設置爲true,即可。如下:
forceCanvas:true
在你創建了canvas元素之後,如果你想要改變它的背景顏色,你需要設置app.renderer.backgroundColor
屬性爲任意的十六進制顏色值("0X"加上"0"~"f"之間的任意6個字符組成的8位字符。)。例如:
app.renderer.backgroundColor = 0x061639;
如果想要獲取canvas的寬和高,可以使用app.renderer.view.width和app.renderer.view.height屬性。
當然,我們也可以改變canvas的大小,只需要使用renderer.resize方法並且傳入width和height屬性的值即可。不過,爲了確保大小能夠正確的適應平臺的分辨率,我們需要將autoResize的值設置爲true。如下:
app.renderer.autoResize = true;
app.renderer.resize(512, 512);//第一個512代表寬度,第二個512代表高度
如果想要canvas填滿整個瀏覽器的窗口,可以提供css樣式,然後將canvas的大小設置爲瀏覽器的窗口大小。如下:
app.renderer.view.style.position = "absolute";
app.renderer.view.style.display = "block";
app.renderer.autoResize = true;
app.renderer.resize(window.innerWidth, window.innerHeight);
但是,如果這樣做了之後,確保使用如下的css代碼來讓所有HTML元素的margin和padding初始化爲0。
* {
margin:0;
padding:0;
}
上面代碼中的星號*是CSS“通用選擇器”,它的意思是“HTML文檔中的所有標記”。
如果希望讓canvas按比例縮放到任何瀏覽器窗口大小,則可以使用自定義的scaleToWindow函數。可以點擊這裏查看更多應用對象配置屬性。
四.核心知識
1.pixi精靈
現在,我們已經有了一個渲染器,或者我們可以稱之爲畫布。我們可以開始往畫布上面添加圖片。我們希望顯示的任何內容都必須被添加到一個叫做stage(舞臺)的特殊pixi對象中。我們可以像如下那樣使用這個舞臺:
//app爲實例化的應用對象
let stage = app.stage;//現在這個stage變量就是舞臺對象
stage是一個特殊的pixi容器對象。我們可以將它看作是一個空盒子,然後和我們添加進去的任何內容組合在一起,並且存儲我們添加的任何內容。stage對象是場景中所有可見對象的根容器。無論我們在舞臺中放入什麼內容都會在畫布中呈現。現在我們的這個盒子還是空的,沒有什麼內容,但是我們很快就會放點內容進去。可以點擊這裏查看關於pixi容器對象的更多信息。
重要說明: 因爲stage是一個pixi容器。所以它擁有有和其它任何容器一樣的屬性方法。雖然stage擁有width和height屬性,但是它們並不指的是渲染窗口的大小。stage的width和height屬性僅僅是爲了告訴我們放在裏面的東西所佔據的區域——更多關於它的信息在前面。
那麼我們應該在舞臺上面放些什麼東西呢?就是一些被稱作爲精靈的特殊圖片對象。精靈基本上就是我們能用代碼控制的圖片。我們可以控制它們的位置,大小,以及其它用於動畫和交互的有用的屬性。學會如何製作與控制一個精靈真的是一件關於學習如何使用pixi.js的重要的事情。如果我們知道如何製作精靈並且能夠把它們添加到舞臺中,我們距離製作遊戲僅剩一步之遙。
pixi有一個精靈類,它是一種製作遊戲精靈的多功能的方式。有三種主要的方式來創建它們:
1.從單個圖片文件中創建。
2.用一個 雪碧圖來創建。雪碧圖是一個放入了你遊戲所需的所有圖像的大圖。
3.從紋理圖集(一個JSON文件,在雪碧圖中定義圖片大小和位置)。
我們將學習這三種方式。但在此之前,我們先了解一下我們需要了解的圖片,然後使用pixi顯示這些圖片。
2.將圖像加載到紋理緩存中
由於pixi使用webGL在GPU上渲染圖片,所以圖片需要採用GPU能夠處理的格式。使用webGL渲染的圖像就被叫做紋理。在你讓精靈顯示圖片之前,需要將普通的圖片轉化成WebGL紋理。爲了讓所有東西在幕後快速有效地工作,Pixi使用紋理緩存來存儲和引用精靈所需的所有紋理。紋理的名稱是與它們引用的圖像的文件位置匹配的字符串。 這意味着如果你有一個從"images/cat.png"加載的紋理,你可以在紋理緩存中找到它,如下所示:
PIXI.utils.TextureCache["images/cat.png"];
紋理以WebGL兼容格式存儲,這對於Pixi的渲染器來說非常有效。然後,您可以使用Pixi的Sprite類使用紋理製作新的精靈。
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() {
//此代碼將在加載程序加載完圖像時運行
}
如果使用loader來加載圖像,這是pixi開發團隊的建議。我們應該通過引入loader's resources對象中的圖片資源來創建精靈,就像如下這樣:
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);
當然加載程序還允許我們加載JSON文件。這在後面,我們會學到。
3.顯示精靈
在我們已經加載了圖片,並且將圖片製作成了精靈,我們需要使用stage.addChild方法來將精靈添加到pixi的stage(舞臺)中。就像如下這樣:
//cat表示精靈變量
app.stage.addChild(cat);
注: 記住stage(舞臺)是所有被添加的精靈的主容器元素。而且我們不會看到任何我們所構建的精靈,除非我們已經把它們添加到了stage(舞臺)中。
好的,讓我們來看一個示例關於如何使用代碼來學會在舞臺上顯示一張單圖圖像。我們假定在目錄examples/images下,你會發現有一張寬64px高64px的貓圖。如下所示:
以下是一個需要加載圖片,創建一個精靈,並且顯示在pixi的stage上的所有JavaScript代碼:
//創建一個pixi應用對象
let app = new PIXI.Application({
width: 256,
height: 256,
antialias: true,
transparent: false,
resolution: 1
}
);
//將canvas元素添加到body元素中
document.body.appendChild(app.view);
//加載圖像,然後使用setup方法運行
//"images/cat.png"這個路徑需要根據自己情況所調整
PIXI.loader.add("images/cat.png").load(setup);
//當圖片加載完成,setup方法會執行
function setup() {
//創建cat精靈,"images/cat.png"這個路徑需要根據自己情況所調整
let cat = new PIXI.Sprite(PIXI.loader.resources["images/cat.png"].texture);
//將cat精靈添加到舞臺中
app.stage.addChild(cat);
}
當代碼運行在瀏覽器上,你會看到如圖所示:
在線示例。
如果我們需要從舞臺上移除精靈,我們可以使用removeChild方法。如下:
//參數爲精靈圖的路徑
app.stage.removeChild(anySprite)
但是通常將精靈的visible屬性設置爲false將是使精靈消失的更簡單,更有效的方法。如下:
//anySprite爲精靈對象,例如前面示例的cat
anySprite.visible = false;
4.使用別名
當然我們也可以對我們使用頻繁的pixi對象和方法創建一些簡略的可讀性更好的別名。例如,你難道想給所有的pixi對象添加PIXI前綴嗎?如果不這樣想,那就給它一個簡短的別名吧。例如:以下是爲TextureCache對象所創建的一個別名。
let TextureCache = PIXI.utils.TextureCache;
然後,使用該別名代替原始別名,如下所示:
//"images/cat.png"這個路徑需要根據自己情況所調整
let texture = TextureCache["images/cat.png"];
使用別名給寫出簡潔的代碼提供了額外的好處:他幫助你緩存了Pixi的常用API。如果Pixi的API在將來的版本里改變了-沒準他真的會變!你將會需要在一個地方更新這些對象和方法,你只用在工程的開頭而不是所有的實例那裏!所以Pixi的開發團隊想要改變它的時候,你只用一步即可完成這個操作! 來看看怎麼將所有的Pixi對象和方法改成別名之後,來重寫加載和顯示圖像的代碼。
//別名
let Application = PIXI.Application;
let loader = PIXI.loader;
let resources = PIXI.loader.resources;
let Sprite = PIXI.Sprite;
//創建一個應用對象
let app = new Application({
width: 256,
height: 256,
antialias: true,
transparent: false,
resolution: 1
}
);
//將Pixi自動爲您創建的畫布添加到HTML文檔中
document.body.appendChild(app.view);
//加載圖像並完成後運行“setup”函數
loader.add("images/cat.png").load(setup);
//該“setup”函數將在圖像加載後運行
function setup() {
//創建一個cat精靈類
let cat = new Sprite(resources["images/cat.png"].texture);
//將cat精靈類添加到舞臺中
app.stage.addChild(cat);
}
大多數教程中的例子將會使用Pixi的別名來處理。除非另有說明,否則你可以假定下面所有的代碼都使用了這些別名。這就是我們所需要知道的所有的關於加載圖像和創建精靈的知識。
5.有關加載的更多信息
前面所顯示的格式是建議用作加載圖像和顯示圖片的標準模板的格式。 因此,我們可以放心地忽略接下來的幾段內容,而直接跳到下一部分“定位精靈”。 但是Pixi的加載程序對象非常複雜,即使您不定期使用它們,也要注意一些功能。 讓我們看一些最有用的。
(1).從普通的JavaScript Image對象或Canvas生成精靈
爲了優化和提高效率,始終最好從預先加載到Pixi的紋理緩存中的紋理製作精靈。 但是,如果由於某種原因需要從常規的JavaScript Image對象製作紋理,則可以使用Pixi的BaseTexture和Texture類來實現:
//參數爲任何JavaScriptImage對象
let base = new PIXI.BaseTexture(anyImageObject);
let texture = new PIXI.Texture(base);
let sprite = new PIXI.Sprite(texture);
如果要從任何現有的canvas元素製作紋理,可以使用BaseTexture.fromCanvas:
//參數爲任何canvas元素
let base = new PIXI.BaseTexture.fromCanvas(anyCanvasElement);
如果要更改精靈顯示的紋理,請使用texture屬性。 將其設置爲任何texture對象,如下所示:
anySprite.texture = PIXI.utils.TextureCache["anyTexture.png"];
如果遊戲中發生重大變化,就可以使用此技術交互式地更改精靈的外觀。
(2).爲加載文件分配名稱
可以爲要加載的每個資源分配一個唯一的名稱。只需提供名稱(字符串)作爲add方法中的第一個參數即可。 例如,以下是將cat的圖像命名爲catImage的方法。
//第一個參數爲分配的別名,第二個參數則是圖像路徑
PIXI.loader.add("catImage", "images/cat.png").load(setup);
這將在loader.resources中創建一個名爲catImage的對象。 這意味着可以通過引用catImage對象來創建一個精靈,如下所示:
//catImage對象下的texture屬性
let cat = new PIXI.Sprite(PIXI.loader.resources.catImage.texture);
但是,建議不要使用此功能! 這是因爲使用它就必須記住爲每個已加載文件指定的所有名稱,並確保不要意外地多次使用同一名稱。正如在前面的示例中所做的那樣,使用文件路徑名更加簡單,並且不容易出錯。
(3).監聽加載進度
Pixi的加載程序有一個特殊的progress事件,它將調用一個可自定義的函數,該函數將在每次文件加載時運行。進度事件由加載器的on方法調用,如下所示:
//loadProgressHandler爲處理進度的函數
PIXI.loader.on("progress", loadProgressHandler);
以下爲在加載鏈中使用包括on方法的方式,並在每次文件加載時調用用戶定義的函數loadProgressHandler。
//使用on方法
PIXI.loader.add([
"images/one.png",
"images/two.png",
"images/three.png"
]).on("progress", loadProgressHandler).load(setup);
//loadProgressHandler函數
function loadProgressHandler() {
console.log("loading");
}
//setup函數
function setup() {
console.log("setup");
}
每次加載其中一個文件時,進度事件都會調用loadProgressHandler以在控制檯中顯示“loading”。當所有三個文件都加載完畢後,setup函數將運行。 以下是上述代碼在控制檯中的輸出:
loading
loading
loading
setup
這很不錯了,但是會變得更好。我們還可以準確地找到已加載的文件以及當前已加載的文件總數的百分比。只需要通過向loadProgressHandler添加可選的loader和resource參數來做到這一點,如下所示:
function loadProgressHandler(loader, resource) {
//從resouce中取得已加載的文件或者取得已加載文件的百分比
}
然後,可以使用resource.url查找當前加載的文件。(如果要查找可能已分配給文件的可選名稱,請使用resource.name作爲add方法中的第一個參數。)然後,您可以使用loader.progress查找當前已加載的總資源百分比。以下是一些執行此操作的代碼。
PIXI.loader.add([
"images/one.png",
"images/two.png",
"images/three.png"
]).on("progress", loadProgressHandler).load(setup);
function loadProgressHandler(loader, resource) {
//顯示當前加載的文件路徑
console.log("loading: " + resource.url);
//顯示當前文件加載的百分比
console.log("progress: " + loader.progress + "%");
//如果第一個參數提供的是文件的可選名稱
//那麼在add方法裏就要像如下這樣接收它們
//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.error會告訴您嘗試加載文件時發生的任何可能的錯誤。resource.data允許您訪問文件的原始二進制數據。
6.有關loader的更多信息
Pixi的loader具有豐富的功能和可配置性。讓我們快速瞭解一下它的用法,好入門。 loader的可鏈接的add方法包含4個基本參數:
add(name, url, optionObject, callbackFunction);
以下是對這些參數做描述的簡單文檔:
1.name (string):要加載的資源的名稱。如果未被使用,則會自動使用url。
2.url (string):此資源的網址,相對於loader的baseUrl。
3.options (object literal):加載的選項。
4.options.crossOrigin (Boolean):請求是跨域的嗎? 默認爲自動確定。
5.options.loadType:資源應如何加載? 默認值爲Resource.LOAD_TYPE.XHR。
6.options.xhrType:使用XHR時應如何執行正在加載的數據?默認值爲Resource.XHR_RESPONSE_TYPE.DEFAULT。
7.callbackFunction:當資源完成加載時所要調用的函數(回調函數)。
這些參數中唯一需要傳入的就是url(要加載的文件)。以下是一些可以使用add方法加載文件的方式的示例。 這是文檔稱爲loader的“常規語法”:
//第一個參數爲加載資源的名稱,第二個參數爲資源路徑,然後第三個參數可不傳,也就是加載的選項,第四個參數就是回調函數
PIXI.loader.add('key', 'http://...', function () {});
PIXI.loader.add('http://...', function () {});
PIXI.loader.add('http://...');
以下這些是loader的“對象語法”的示例:
PIXI.loader.add({
name: 'key2',
url: 'http://...'
}, function () {})
PIXI.loader.add({
url: 'http://...'
}, function () {})
PIXI.loader.add({
name: 'key3',
url: 'http://...'
onComplete: function () {}
})
PIXI.loader.add({
url: 'https://...',
onComplete: function () {},
crossOrigin: true
})
您還可以將對象或URL或兩者的數組傳遞給add方法:
PIXI.loader.add([
{name: 'key4', url: 'http://...', onComplete: function () {} },
{url: 'http://...', onComplete: function () {} },
'http://...'
]);
注意: 如果需要重置loader以加載新一批文件,請調用loader的reset方法:PIXI.loader.reset()。
Pixi的loader具有許多更高級的功能,包括使我們可以加載和解析所有類型的二進制文件的選項。這不是我們日常要做的事情,並且超出了目前我們所學習的範圍,因此可以從GitHub項目中獲取更多信息。
7.定位精靈
現在我們知道了如何創建和顯示精靈,讓我們瞭解如何放置和調整精靈的大小。在前面的示例中,cat sprite已添加到舞臺的左上角。cat的x位置爲0,y位置爲0。可以通過更改cat的x和y屬性的值來更改cat的位置。通過將cat的x和y屬性值設置爲96的方法,可以使cat在舞臺中居中。
cat.x = 96;
cat.y = 96;
創建精靈後,將以上兩行代碼添加到setup函數內的任何位置。
function setup() {
//創建cat精靈
let cat = new Sprite(resources["images/cat.png"].texture);
//改變精靈的位置
cat.x = 96;
cat.y = 96;
//將cat精靈添加到舞臺中如此便可以看到它
app.stage.addChild(cat);
}
注意: 在這個例子中,Sprite是PIXI的別名。Sprite,TextureCache是PIXI.utils.TextureCache的別名,resources是PIXI.loader.resources的別名。後面都是使用別名,並且從現在開始,示例代碼中所有Pixi對象和方法的格式都相同。
這兩行代碼會將cat右移96像素,向下移96像素。結果如下:
在線示例。
cat的左上角(左耳)代表其x和y錨點。要使cat向右移動,請增加其x屬性的值。要使cat向下移動,請增加其y屬性的值。如果cat的x值爲0,則它將位於舞臺的最左側。如果y值爲0,則它將位於該階段的頂部。如下圖所示:
其實可以不必單獨設置精靈的x和y屬性,而是可以在一行代碼中將它們一起設置,如下所示:
//也就是調用set方法即可,傳入修改的x參數和y參數
sprite.position.set(x, y)
讓我們來看看以上的示例代碼修改之後的結果:
在線示例。
可以看出來,結果都是一樣的。
8.大小和比例
我們可以通過設置精靈的width和height屬性來更改其大小。以下便是一個示例,將cat設置爲80像素的寬度和120像素的高度。
cat.width = 80;
cat.height = 120;
將這兩行代碼添加到setup函數中,就像如下:
function setup() {
//創建cat精靈
let cat = new Sprite(resources["images/cat.png"].texture);
//改變精靈的位置
cat.x = 96;
cat.y = 96;
//改變精靈的大小
cat.width = 80;
cat.height = 120;
//將cat精靈添加到舞臺中如此便可以看到它
app.stage.addChild(cat);
}
效果如圖所示:
在線示例。
我們會看到cat的位置(左上角)沒有變化,只是寬度和高度有變化。如下圖所示:
精靈還具有scale.x和scale.y屬性,可按比例更改精靈的寬度和高度。以下是將cat的scale設置爲一半尺寸的方法:
cat.scale.x = 0.5;
cat.scale.y = 0.5;
scale是介於0和1之間的數字,代表精靈大小的百分比。1表示100%(原尺寸),而0.5表示50%(半尺寸)。您可以通過將精靈的scale設置爲2來使精靈大小增加一倍,如下所示:
cat.scale.x = 2;
cat.scale.y = 2;
Pixi提供了另一種簡潔的方法,您可以使用scale.set方法在一行代碼中設置精靈的縮放比例。
//注意參數代表的意思
cat.scale.set(0.5, 0.5);
如果喜歡這樣使用,那就這樣用吧!我們來看一個完整的示例:
//別名
let Application = PIXI.Application;
let loader = PIXI.loader;
let resources = PIXI.loader.resources;
let Sprite = PIXI.Sprite;
//創建一個應用對象
let app = new Application({
width: 256,
height: 256,
antialias: true,
transparent: false,
resolution: 1
});
//將Pixi自動爲您創建的畫布添加到HTML文檔中
document.body.appendChild(app.view);
//加載圖像並完成後運行“setup”函數
loader.add("/static/page/PIXIJS/images/cat.png").load(setup);
//該“setup”函數將在圖像加載後運行
function setup() {
//創建cat精靈
let cat = new Sprite(resources["/static/page/PIXIJS/images/cat.png"].texture);
//改變精靈的位置
cat.position.set(96, 96);
//改變精靈的大小
cat.scale.set(0.5,0.5);
//或者這樣使用
//cat.scale.x=0.5
//cat.scale.y=0.5
//將cat精靈添加到舞臺中如此便可以看到它
app.stage.addChild(cat);
}
運行效果如圖所示:
在線示例。
9.旋轉
我們也可以通過將精靈的rotation屬性設置爲以弧度爲單位的值來使其旋轉。如下所示:
cat.rotation = 0.5;
但是旋轉發生在哪一點附近?我們可以從下圖中看到精靈的左上角代表其x和y位置。該點稱爲錨點。 如果將精靈的rotation屬性設置爲0.5,則旋轉將圍繞精靈的錨點進行。我們也會知道這將對我們的cat精靈產生什麼影響。
我們會看到錨點,即cat的左耳,是cat圍繞其旋轉的假想圓的中心。 如果要讓精靈圍繞其中心旋轉怎麼辦?更改精靈的錨點,使其居中,如下所示:
//anchor就是錨點
cat.anchor.x = 0.5;
cat.anchor.y = 0.5;
anchor.x和anchor.y值代表紋理尺寸的百分比,範圍爲0到1(0%到100%)。將其設置爲0.5可使紋理在該點上居中。點本身的位置不會改變,只是紋理在其上定位的方式一樣。下一張圖顯示瞭如果將居中的錨點居中,旋轉的精靈會發生什麼。
我們會看到精靈的紋理向上和向左移動。這是要記住的重要副作用!就像position和scale一樣,我們也可以使用以下一行代碼來設置錨點的x和y值:
//注意參數即可
cat.anchor.set(x, y);
精靈還具有pivot屬性,其作用方式類似於anchor。 pivot設置精靈的x / y原點的位置。 如果更改軸心點然後旋轉精靈,它將圍繞該原點旋轉。例如,下面的代碼將把精靈的pivot.x指向32,將其pivot.y指向32。
//注意參數的意義
cat.pivot.set(32, 32);
假設精靈爲64x64像素,則精靈現在將圍繞其中心點旋轉。但是請記住:如果更改了精靈的pivot,則還更改了它的x / y原點。那麼,anchor和pivot點有什麼區別?他們真的很相似!anchor使用0到1歸一化的值移動精靈圖像紋理的原點。pivot使用像素值移動精靈的x和y的原點。我們應該使用哪個?由我們自己決定。喜歡用哪個就用哪個即可。讓我們來看看使用這兩個屬性的完整示例吧!
10.從精靈雪碧圖中製作精靈
現在,我們也知道了如何從單個圖像文件製作精靈。但是,作爲遊戲設計師,通常會使用雪碧圖(也稱爲精靈圖)來製作精靈。Pixi具有一些方便的內置方法來幫助我們完成此任務。所謂的雪碧圖就是包含子圖像的單個圖像文件。子圖像代表要在遊戲中使用的所有圖形。以下是圖塊圖像的示例,其中包含遊戲角色和遊戲對象作爲子圖像。
整個雪碧圖爲192 x 192像素。每個圖像都位於其自己的32 x 32像素網格單元中。在圖塊上存儲和訪問所有遊戲圖形是一種處理圖形的非常高效的處理器和內存方式,Pixi爲此進行了優化。 我們可以通過定義與我們要提取的子圖像大小和位置相同的矩形區域,來從雪碧圖中捕獲子圖像。以下是從雪碧圖中提取的火箭子圖像的示例。
讓我們看看執行此操作的代碼。首先,就像在前面的示例中所做的那樣,使用Pixi的loader加載tileset.png圖像。
//注意這裏的路徑依據實際情況來修改調整
loader.add("images/tileset.png").load(setup);
接下來,在加載圖像後,使用雪碧圖的矩形塊來創建精靈的圖像。以下是提取子圖像,創建火箭精靈並將其定位並顯示在畫布上的代碼。
function setup() {
//從紋理創建“tileset”精靈
let texture = TextureCache["images/tileset.png"];
//創建一個定義位置矩形對象
//並且要從紋理中提取的子圖像的大小
//`Rectangle`是`PIXI.Rectangle`的別名,注意這裏的參數,後續會詳解,參數值與實際情況有關
let rectangle = new Rectangle(192, 128, 64, 64);
//告訴紋理使用該矩形塊
texture.frame = rectangle;
//從紋理中創建一個精靈
let rocket = new Sprite(texture);
//定位火箭精靈在canvas畫布上
rocket.x = 32;
rocket.y = 32;
//將火箭精靈添加到舞臺中
app.stage.addChild(rocket);
//重新渲染舞臺
app.renderer.render(app.stage);
}
這是如何工作的?Pixi具有內置的Rectangle對象(PIXI.Rectangle),它是用於定義矩形形狀的通用對象。它有四個參數。前兩個參數定義矩形的x和y位置。最後兩個定義其寬度和高度。這是定義新Rectangle對象的格式。
let rectangle = new PIXI.Rectangle(x, y, width, height);
矩形對象只是一個數據對象。由我們自己來決定如何使用它。在我們的示例中,我們使用它來定義要提取的圖塊上的子圖像的位置和區域。Pixi紋理具有一個有用的屬性,稱爲frame,可以將其設置爲任何Rectangle對象。frame將紋理裁剪爲矩形的尺寸。以下是使用frame將紋理裁剪爲火箭的大小和位置的方法。
let rectangle = new Rectangle(192, 128, 64, 64);
texture.frame = rectangle;
然後,我們就可以使用該裁剪的紋理來創建精靈:
let rocket = new Sprite(texture);
然後它就開始運行啦。由於我們會頻繁地使用雪碧圖製作精靈紋理,因此Pixi提供了一種更方便的方法來幫助我們完成此任務-讓我們繼續下一步。
在線示例。
11.使用紋理圖集
如果我們是開發大型複雜的遊戲,則需要一種快速有效的方法來從雪碧圖創建精靈。這是紋理圖集真正有用的地方。紋理圖集是JSON數據文件,其中包含匹配的圖塊PNG圖像上子圖像的位置和大小。如果使用紋理圖集,那麼關於要顯示的子圖像,我們所需要知道的就是它的名稱。我們可以按任何順序排列雪碧圖圖像,JSON文件將爲我們跟蹤其大小和位置。這真的很方便,因爲這意味雪碧圖圖片的大小和位置不會硬編碼到我們的遊戲程序中。如果我們對雪碧圖進行更改(例如添加圖像,調整圖像大小或將其刪除),則只需重新發布JSON文件,我們的遊戲就會使用該數據顯示正確的圖像。我們無需對遊戲代碼進行任何更改。
Pixi與一種流行的名爲Texture Packer的軟件工具輸出的標準JSON紋理圖集格式兼容。Texture Packer的“基本”許可證是免費的。讓我們瞭解如何使用它製作紋理圖集,並將該圖集加載到Pixi中。(我們也可以不必使用Texture Packer。類似的工具(例如Shoebox或spritesheet.js)可以以與Pixi兼容的標準格式輸出PNG和JSON文件。)
首先,要收集在遊戲中使用的單個圖像文件。
注: (本文中的所有圖像均由Lanea Zimmerman創建。您可以在此處找到她的更多作品。謝謝Lanea Zimmerman!)
接下來,打開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按鈕。選擇文件名和存儲位置,然後保存發佈的文件。 最終將獲得2個文件:一個PNG文件和一個JSON文件。 在此示例中,文件名是treasureHunter.json和treasureHunter.png。爲了簡便點,只需將兩個文件都保存在一個名爲images的文件夾中。(可以將JSON文件視爲圖像文件的額外元數據,因此將兩個文件都保留在同一文件夾中是很有意義的。)JSON文件描述了圖集中每個子圖像的名稱,大小和位置。如以下摘錄了一個文件內容,描述了Blob Monster(泡泡怪)子圖像。
"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”。
使用紋理圖集的衆多優點之一是,可以輕鬆地在每個圖像周圍添加2個像素的填充(默認情況下,Texture Packer會這樣做。)這對於防止紋理滲漏的可能性很重要。紋理出血(注:出血是排版和圖片處理方面的專有名詞,指在主要內容周圍留空以便印刷或裁切)是當圖塊上相鄰圖像的邊緣出現在精靈旁邊時發生的一種效果。發生這種情況的原因是計算機的GPU(圖形處理單元)決定如何舍入小數像素值的方式。它應該向上或向下取整?每個GPU都不同。在GPU上的圖像周圍留出1或2個像素的間距,可使所有圖像始終顯示一致。
注:(如果圖形周圍有兩個像素填充,並且在Pixi的顯示方式中仍然發現奇怪的“偏離一個像素”故障,請嘗試更改紋理的縮放模式算法。方法如下:
texture.baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;
。由於GPU浮點舍入錯誤,有時會發生這些故障。)
現在我們已經知道如何創建紋理圖集,讓我們瞭解如何將其加載到遊戲代碼中。
ps:關於以上的示例所涉及到的圖片資源可點擊此處下載。
下圖爲本人使用Texture Packer創建的紋理圖集的一個展示:
可點擊此處(JSON), 此處(png)下載已經創建的紋理圖集JSON文件和PNG文件。
12.加載紋理圖集
可以使用Pixi的loader來加載紋理貼圖集。如果是用Texture Packer生成的JSON,loader會自動讀取數據,並對每一個幀創建紋理。下面就是怎麼用loader來加載treasureHunter.json。當它成功加載,setup方法將會執行。
//路徑與實際項目有關
loader.add("images/treasureHunter.json").load(setup);
現在,紋理圖集上的每個圖像都是Pixi緩存中的單個紋理。我們可以使用與Texture Packer中相同的名稱(“ blob.png”,“ dungeon.png”,“ explorer.png”等)訪問緩存中的每個紋理。
13.從加載的紋理圖集創建精靈。
Pixi提供了三種從紋理圖集創建精靈的方式:
1.使用TextureCache:
let texture = TextureCache["frameId.png"],
sprite = new Sprite(texture);
2.如果使用的是pixi的loader來加載紋理貼圖集,則使用loader的 resources屬性。
let sprite = new Sprite(resources["images/treasureHunter.json"].textures["frameId.png"]);
3.要創建一個精靈需要寫太多東西了!所以建議給紋理貼圖集的textures對象創建一個叫做id的別名,就像是這樣:
let id = PIXI.loader.resources["images/treasureHunter.json"].textures;
現在就可以像這樣實例化一個精靈了:
let sprite = new Sprite(id["frameId.png"]);
這真的太棒了!
以下爲在setup函數中如何使用這三種不同的方法來創建和顯示dungeon,explorer,和treasure精靈。
//定義這三個變量,方便之後的使用
let textureId;
//地牢
let dungeon;
//探險者
let explorer;
//寶藏
let treasure;
//setup函數
function setup(){
//有3種不同的方式來創建和顯示精靈
//第一種,使用紋理別名,TextureCache爲PIXI.utils.TextureCache的別名
let dungeonTexture = TextureCache['dungeon.png'];
//Sprite爲PIXI.Sprite的別名
dungeon = new Sprite(dungeonTexture);
//調用addChild方法將精靈添加到舞臺中
app.stage.addChild(dungeon);
//第二種,使用resources來創建,也要注意參數根據實際情況來寫
explorer = new Sprite(resources["images/treasureHunter.json"].textures['explorer.png']);
//將探險者的座標設置一下,也就是設置探險者的位置,探險者在舞臺中間,x方向距離隨便設置
explorer.x = 68;
explorer.y = app.stage.height / 2 - explorer.height / 2;
app.stage.addChild(explorer);
//爲所有的紋理圖集創建一個別名
textureId = PIXI.loader.resources['images/treasureHunter.json'].textures;
treasure = new Sprite(textureId["treasure.png"]);
//將寶藏的座標設置一下
treasure.x = app.stage.width - treasure.width - 48;
treasure.y = app.stage.height / 2 - treasure.height / 2;
//將寶藏精靈添加到舞臺中去
app.stage.addChild(treasure);
}
下圖爲以上代碼所展現的結果:
舞臺尺寸爲512 x 512像素,您可以在上面的代碼中看到app.stage.height和app.stage.width屬性用於對齊精靈。 以下是瀏覽器的y位置垂直居中的方式:
explorer.y = app.stage.height / 2 - explorer.height / 2;
學習使用紋理圖集創建和顯示精靈是一個重要的基本操作。因此,在繼續之前,我們再來編寫用於添加其餘精靈的代碼:blobs和exit,這樣您便可以生成如下所示的場景:
以下是完成所有這些操作的全部代碼。還包括了HTML代碼,因此可以在適當的上下文中查看所有內容。(可以在此處下載代碼。)請注意,已創建了blobs精靈,並將其添加到循環中的舞臺上,並分配了隨機位置。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>從紋理圖中創建精靈</title>
</head>
<body>
<script src="https://www.eveningwater.com/static/data/web/PixiJS/source/dist/pixi.min.js"></script>
<script>
//別名
let Application = PIXI.Application,
Container = PIXI.Container,
loader = PIXI.loader,
resources = PIXI.loader.resources,
TextureCache = PIXI.utils.TextureCache,
Sprite = PIXI.Sprite,
Rectangle = PIXI.Rectangle;
//創建pixi應用對象
let app = new Application({
width: 512,
height: 512,
antialiasing: true,
transparent: false,
resolution: 1
});
//將應用對象添加到dom中
document.body.appendChild(app.view);
//加載json文件,並在加載完成之後執行setup函數,注意這裏的json文件路徑,後面的也是
loader.add("./texture.json").load(setup);
//定義一些需要用到的變量
let dungeon, explorer, treasure, door, textureId;
function setup() {
//以下分別使用三種不同的方式來創建精靈
//第一種
let dungeonTexture = TextureCache["dungeon.png"];
dungeon = new Sprite(dungeonTexture);
app.stage.addChild(dungeon);
//第二種
explorer = new Sprite(
resources["./texture.json"].textures["explorer.png"]
);
explorer.x = 68;
//設置探險者的位置
explorer.y = app.stage.height / 2 - explorer.height / 2;
app.stage.addChild(explorer);
//第三種
textureId = PIXI.loader.resources["./texture.json"].textures;
treasure = new Sprite(textureId["treasure.png"]);
//設置寶藏的位置
treasure.x = app.stage.width - treasure.width - 48;
treasure.y = app.stage.height / 2 - treasure.height / 2;
app.stage.addChild(treasure);
//創建出口的精靈
door = new Sprite(textureId["door.png"]);
door.position.set(32, 0);
app.stage.addChild(door);
//製作泡泡怪精靈
let numberOfBlobs = 6,//數量
spacing = 48,//位置
xOffset = 150;//偏移距離
//根據泡泡怪精靈的數量來製作精靈
for (let i = 0; i < numberOfBlobs; i++) {
let blob = new Sprite(textureId["blob.png"]);
let x = spacing * i + xOffset;
//隨機生成泡泡怪的位置
let y = randomInt(0, app.stage.height - blob.height);
// 設置泡泡怪的位置
blob.x = x;
blob.y = y;
//將泡泡怪添加到舞臺中
app.stage.addChild(blob);
}
}
//隨機生成的函數
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
</script>
</body>
</html>
在線示例。
我們可以在上面的代碼中看到所有的blob都是使用for循環創建的。每個blobs沿x軸均勻分佈,如下所示:
let x = spacing * i + xOffset;
blob.x = x;
spacing的值爲48,xOffset的值爲150。這意味着第一個Blob的x位置爲150。這會將其從舞臺的左側偏移150個像素。每個後續的Blob的x值將比循環的上一次迭代中創建的Blob大48個像素。這樣沿着地牢地板從左到右創建了一條均勻分佈的怪物線。
每個blob也被賦予一個隨機的y位置。以下爲執行此操作的代碼:
let y = randomInt(0, stage.height - blob.height);
blob.y = y;
可以爲blob的y位置分配介於0到512之間的任何隨機數,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是一個很好的用來做遊戲的工具函數,在寫遊戲的時候會經常用到它。
14.移動精靈
我們現在知道了如何顯示精靈,但是如何使它們移動呢?這很容易:使用Pixi的代碼創建循環功能,這稱爲遊戲循環。放入遊戲循環中的任何代碼都會每秒更新60次。可以編寫以下代碼來使cat精靈以每幀1個像素的速率向右移動。
function setup() {
//開始遊戲循環,創建一個這樣的函數
//Pixi的`ticker`提供了一個delta參數
app.ticker.add(delta => gameLoop(delta));
}
function gameLoop(delta){
//是cat精靈移動1px
cat.x += 1;
}
如果運行這段代碼,我們將看到精靈逐漸移到舞臺的右側。
這是因爲每次gameLoop運行時,它都會將cat的x位置加1。
cat.x += 1;
每一個你放進Pixi的ticker的函數都會每秒被執行60次。你可以看見函數裏面提供了一個delta的內容,他是什麼呢?delta的值代表幀的部分的延遲。可以把它添加到cat的位置,讓cat的速度和幀率無關。下面是代碼:
cat.x += 1 + delta;
是否選擇添加此增量值在很大程度上是美學選擇。而且只有當您的動畫努力保持每秒60幀的一致顯示速率時,效果纔會真正顯着(例如,如果它在慢速設備上運行,則可能會發生這種情況)。本文中的其餘示例將不使用此增量值,但可以根據需要在自己的工作中隨意使用它。 可以不必使用Pixi的代碼來創建遊戲循環。如果願意的話,可以使用requestAnimationFrame,如下所示:
function gameLoop() {
//每60秒執行一次遊戲循環函數
requestAnimationFrame(gameLoop);
//移動cat精靈
cat.x += 1;
}
//開始遊戲循環
gameLoop();
採用哪種方式隨我們自己的喜好。這就是移動精靈所有的操作!只需在循環內以較小的增量更改任何sprite屬性,它們就會隨着時間推移進行動畫處理。如果要讓精靈沿相反的方向(向左)設置動畫,只需給它指定一個負值即可,例如-1。
以下是以上示例的完整代碼:
//別名
let Application = PIXI.Application,
Container = PIXI.Container,
loader = PIXI.loader,
resources = PIXI.loader.resources,
TextureCache = PIXI.utils.TextureCache,
Sprite = PIXI.Sprite,
Rectangle = PIXI.Rectangle;
//創建應用對象
let app = new Application({
width: 256,
height: 256,
antialias: true,
transparent: false,
resolution: 1
});
//將應用對象添加到dom中
document.body.appendChild(app.view);
// 加載圖像資源
loader.add("images/cat.png").load(setup);
//定義cat精靈
let cat;
function setup() {
//創建cat精靈
cat = new Sprite(resources["images/cat.png"].texture);
cat.y = 96;
app.stage.addChild(cat);
//開始遊戲循環
app.ticker.add(delta => gameLoop(delta));
}
function gameLoop(delta) {
// 移動1像素
cat.x += 1;
//也可以使用增量
//cat.x += 1 + delta;
}
在線示例。
注意: (cat變量需要在setup和gameLoop函數之外定義,以便可以在兩個函數中進行訪問。)
我們還可以爲精靈的比例,旋轉或大小設置動畫-無論如何!我們將看到更多有關如何爲精靈設置動畫的示例。
15.使用速度屬性
爲了方便自己,給遊戲增加點靈活性,最好使用兩個速度屬性(vx和vy)控制精靈的移動速度。vx用於設置精靈在x軸上(水平)的速度和方向。vy用於在y軸上(垂直)設置精靈的速度和方向。與其直接更改精靈的x和y值,不如先更新速度變量,然後將這些速度值分配給精靈。這是交互式遊戲動畫所需的額外的一個模塊。
第一步是在精靈上創建vx和vy屬性,併爲其賦予初始值。
cat.vx = 0;
cat.vy = 0;
設置vx和vy的值爲0意味着精靈並沒有被移動(即靜止)。接着,在遊戲循環中,更新我們想要移動的vx和vy的速度值,然後把它們賦值給精靈的x和y屬性。以下是使用這種方式來使得精靈每幀往右下方移動1像素的示例代碼:
function setup() {
//創建cat精靈
cat = new Sprite(resources["images/cat.png"].texture);
cat.y = 96;
cat.vx = 0;
cat.vy = 0;
app.stage.addChild(cat);
//開始遊戲循環
app.ticker.add(delta => gameLoop(delta));
}
function gameLoop(delta){
//更新cat精靈的速度
cat.vx = 1;
cat.vy = 1;
//將速度屬性值賦值給cat精靈的位置,即x和y座標
cat.x += cat.vx;
cat.y += cat.vy;
}
當運行以上的代碼,cat精靈就會每幀往右下方移動1像素。如下圖所示:
想要讓cat精靈往不同方向移動嗎?讓cat精靈往左移動,那麼就將vx 的值設置爲負數,例如-1。想要讓它往上移動,那麼就將vy的值設置爲負數,例如-1。想要讓cat精靈移動的更快,只需要將vx和vy的值設置大一點,就像3,5,-2,-4。(負號代表方向)。
我們會看到如何通過利用vx和vy的速度值來模塊化精靈的速度,它對遊戲的鍵盤和鼠標控制系統很有幫助,而且更容易實現物理模擬。
在線示例。
16.遊戲狀態
考慮到樣式,也爲了幫助模塊化代碼,個人建議還是像下面這樣構造遊戲循環:
//定義一個變量設置遊戲開始狀態
let state = play;
app.ticker.add((delta) => { gameLoop(delta)});
//開始遊戲循環
function gameLoop(delta){
//更改遊戲狀態
state(delta);
}
function play(delta){
cat.vx = 1;
cat.x += cat.vx;
}
我們會看到gameLoop每秒60次調用了state函數。state函數是什麼呢?它被賦值爲play。意味着play函數會每秒運行60次。以下的示例展示瞭如何用一個新模式來重構上一個例子的代碼:
//爲了方便其它函數使用變量,將定義全局變量
let cat;
let state;
function setup() {
//創建cat精靈
cat = new Sprite(resources["images/cat.png"].texture);
cat.y = 96;
cat.vx = 0;
cat.vy = 0;
app.stage.addChild(cat);
//開始設置遊戲狀態
state = play;
//開始遊戲循環
app.ticker.add(delta => gameLoop(delta));
}
function gameLoop(delta){
//更新當前的遊戲狀態
state(delta);
}
function play(delta) {
//每幀讓cat移動1像素
cat.vx = 1;
cat.x += cat.vx;
}
是的,也許這有點讓人不快(head-swirler)!但是,不要讓它嚇到我們,而是花一兩分鐘來思考這些功能是如何連接的。正如我們將要看到的那樣,像這樣構造遊戲循環將使進行切換遊戲場景和關卡之類的事情變得非常容易。
在線示例。
17.鍵盤控制運動
只需多做一些工作,我們就可以構建一個簡單的系統來使用鍵盤控制精靈。爲了簡化我們的代碼,建議使用稱爲keyboard的自定義函數來偵聽和捕獲鍵盤事件。如下所示:
function keyboard(value) {
let key = {};
key.value = value;
key.isDown = false;
key.isUp = true;
key.press = undefined;
key.release = undefined;
//鍵盤按下開始操作
key.downHandler = event => {
if (event.keyCode === key.value) {
if (key.isUp && key.press)key.press();
key.isDown = true;
key.isUp = false;
event.preventDefault();
}
};
//鍵盤按下結束
key.upHandler = event => {
if (event.keyCode === key.value) {
if (key.isDown && key.release)key.release();
key.isDown = false;
key.isUp = true;
event.preventDefault();
}
};
//綁定監聽的事件
const downListener = key.downHandler.bind(key);
const upListener = key.upHandler.bind(key);
window.addEventListener("keydown", downListener, false);
window.addEventListener("keyup", upListener, false);
//解綁事件的監聽
key.unsubscribe = () => {
window.removeEventListener("keydown", downListener);
window.removeEventListener("keyup", upListener);
};
return key;
}
keyboard函數用起來很容易,可以像這樣創建一個新的鍵盤對象:
let keyObject = keyboard(keyValue);
它的一個參數是您要收聽的鍵值。可以點擊此處查看鍵盤鍵值列表。然後將press和release方法分配給鍵盤對象,如下所示:
keyObject.press = () => {
//key object pressed
};
keyObject.release = () => {
//key object released
};
鍵盤對象也有isDown和isUp的布爾值屬性,用它們來檢查每個按鍵的狀態。但是 不要忘記使用unsubscribe方法刪除事件偵聽器:
keyObject.unsubscribe();
在examples文件夾裏看一下keyboardMovement.html文件是怎麼用keyboard函數的,利用鍵盤的方向鍵去控制精靈圖。運行它,然後用上下左右按鍵去讓貓在舞臺上移動。
以下是所有的代碼:
let cat;
let state;
function setup(){
//創建cat精靈
cat = new Sprite(resource["./images/cat.png"].texture);
//設置cat精靈的座標
cat.y = 96;
cat.vx = 0;
cat.vy = 0;
//添加到舞臺中
app.stage.addChild(cat);
//鍵盤按鍵事件注意參數爲實際對應的鍵盤key,是整數數字
let left = keyboard("arrowLeft");
let right = keyboard("arrowRight");
let up = keyboard("arrowUp");
let down = keyboard("arrowDown");
//當按下左方向鍵往左改變速度,即改變vx爲負值,在這裏是5,vy不變
left.press = () => {
cat.vx = -5;
cat.vy = 0;
}
//當釋放左方向鍵時初始化速度
left.release = () => {
//如果右鍵沒有被按下,並且cat的vy速度爲0
if(!right.isDown && cat.vy === 0){
cat.vx = 0;
}
}
//當按下右方向鍵
right.press = () => {
cat.vx = 5;
cat.vy = 0;
}
//當釋放右方向鍵
right.release = () => {
if(!left.isDown && cat.vy === 0){
cat.vx = 0;
}
}
//當按下上方向鍵
up.press = () => {
cat.vy = -5;
cat.vx = 0;
}
//當釋放上方向鍵
up.release = () => {
if(!down.isDown && cat.vx === 0){
cat.vy = 0;
}
}
//當按下下方向鍵
down.press = () => {
cat.vy = 5;
cat.vx = 0;
}
//當釋放下方向鍵
down.release = () => {
if(!up.isDown && cat.vx === 0){
cat.vy = 0;
}
}
state = play;
//開始遊戲循環
app.ticker.add((delta) => {
gameLoop(delta);
})
}
function gameLoop(delta){
//更新遊戲狀態
state(delta);
}
function play(delta){
cat.x += cat.vx;
cat.y += cat.vy;
}
在線示例。
18.精靈分組
(1).精靈分組含義
精靈分組使我們可以創建遊戲場景,並將相似的精靈作爲一個單元一起管理。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精靈的根容器。)
以上的代碼效果如下圖所示:
我們是看不到這個包含精靈圖的animals分組的。它僅僅是個容器而已。
現在,我們可以將這個animals分組視爲一個單元。我們也可以將Container視爲一種沒有紋理的特殊精靈。如果需要獲取animals包含的所有子精靈的列表,可以使用children數組來獲取。
console.log(animals.children)
//Displays: Array [Object, Object, Object]
它告訴我們animals有三個子精靈。因爲animals與任何其他Sprite一樣,因此可以更改其x和y值,alpha,scale和所有其他sprite屬性。我們在父容器上更改的任何屬性值都將以相對方式影響子精靈。因此,如果設置了animals的x和y位置,則所有子精靈將相對於animals的左上角重新定位。如果將animals的x和y位置設置爲64,會發生什麼?
animals.position.set(64, 64);
整個精靈組將向右下方移動64個像素。如下圖所示:
animals也有其自己的尺寸,該尺寸基於包含的精靈所佔據的面積。我們可以找到它的寬度和高度值,如下所示:
console.log(animals.width);
//Displays: 112
console.log(animals.height);
//Displays: 112
如果更改animals的寬度或高度會怎樣?
animals.width = 200;
animals.height = 200;
所有子精靈將縮放以匹配該更改。如下圖所示:
我們可以根據需要在其他容器中嵌套儘可能多的容器,以根據需要創建深層次結構。但是,DisplayObject(如Sprite或另一個Container)一次只能屬於一個父級。如果使用addChild將精靈作爲另一個對象的子級,則Pixi會自動將其從當前父級中刪除。這是我們無需擔心的有用的管理。
(2).局部和全局位置
當把精靈添加到一個容器中時,它的x和y是相對於精靈分組的左上角來定位的,這就是精靈的局部位置。例如:你認爲cat精靈在以下圖中所處的位置是?
讓我們來獲取它的值:
console.log(cat.x);
//Displays:16
16?是的!這是因爲cat相對於分組的左上角只僅僅偏移了16像素而已。 16就是cat的局部位置。
精靈當然也有全局位置。全局位置就是舞臺左上角到精靈的錨點(通常值得是精靈的左上角的距離)的距離。我們可以通過toGlobal方法來獲取精靈的全局位置。如下:
//父精靈上的方法,傳入子精靈位置參數
parentSprite.toGlobal(childSprite.position)
以上代碼的意思就是如果我們要在animals分組中找到cat精靈的全局位置,那麼就要像如下這樣寫代碼:
console.log(animals.toGlobal(cat.position));
//Displays: Object {x: 80, y: 80...};
然後它就會給我們一個x和y的位置值,即80。更確切的說,cat的全局位置就是相對於舞臺左上角的位置。
如果不知道精靈的父容器是什麼?我們如何找到精靈的全局位置呢?每個精靈都會有一個parent屬性來告訴我們它的父容器(或者說叫父精靈分組)是什麼。如果將一個精靈正確的添加到了舞臺中,那麼舞臺就是精靈的父容器。在以上的示例中,cat精靈的父容器就是 animals精靈分組。那也就意味着可以通過編寫如下的代碼來獲取cat的全局位置。
cat.parent.toGlobal(cat.position);
即使我們不知道cat精靈的父容器是誰,它一樣會執行。當然還有一種更好的方式來計算出精靈的全局位置,並且它通常也是一種最佳方式,所以聽好啦!如果我們想要知道精靈到canvas左上角的距離,並且不知道或者不關心精靈的父容器是誰,可以使用getGlobalPosition方法。以下展示瞭如何獲取tiger精靈的全局位置:
tiger.getGlobalPosition().x
tiger.getGlobalPosition().y
在我們已經寫好的示例中,它會返回我們x和y的值是128。更特別的是, getGlobalPosition返回的值非常精確:當精靈的局部位置改變的同時,也會返回給我們準確的全局位置。
如果想要將全局位置轉爲局部位置應該怎麼辦?我們可以使用toLocal方法。它的工作方式很類似,通常是以下的格式:
sprite.toLocal(sprite.position, anyOtherSprite)
使用toLocal方法可以找到一個精靈與另一個任意精靈之間的距離。以下代碼展示瞭如何找到tiger相對於 hedgehog的位置。
tiger.toLocal(tiger.position, hedgehog).x
tiger.toLocal(tiger.position, hedgehog).y
上面的代碼會返回一個32的x值和一個32的y值。我們可以在示例圖中看到tiger的左上角和hedgehog的左上角距離32像素。
(3).使用ParticleContainer分組精靈
Pixi有一個額外的,高性能的方式去分組精靈的方法稱作:ParticleContainer(PIXI.particles.ParticleContainer)。任何在ParticleContainer裏的精靈都會比在一個普通的Container的渲染速度快2到5倍。這是用於提升遊戲性能的一個很棒的方法。 可以像這樣創建ParticleContainer:
let superFastSprites = new PIXI.particles.ParticleContainer();
然後用addChild方法去往裏添加精靈,就像往普通的Container添加一樣。
如果使用ParticleContainer,我們就不得不做出一些妥協。在一個ParticleContainer裏的精靈僅僅只有一些基本的屬性: x,y,width,height,scale,pivot,alpha, visible——就這麼多。而且,它包含的精靈不能擁有自己的嵌套子級ParticleContainer也無法使用Pixi的高級視覺效果,例如濾鏡和混合模式。每個ParticleContainer只能用一個紋理(因此,如果您想要具有不同外觀的精靈,則必須使用雪碧圖)。但是對於獲得的巨大性能提升,這些妥協通常是值得的。而且,還可以在同一項目中同時使用Containers和ParticleContainers,因此可以微調優化。
爲什麼在Particle Container的精靈會如此快呢?因爲精靈的位置是直接在GPU上計算的。Pixi開發團隊正在努力讓儘可能多的雪碧圖在GPU上處理,所以很有可能用的最新版的Pixi的 ParticleContainer的特性一定比現在在這兒描述的特性多得多。查看當前ParticleContainer文檔以獲取更多信息。
無論在哪裏創建一個ParticleContainer,都會有四個屬性參數需要提供: size,properties,batchSize,autoResize。
let superFastSprites = new ParticleContainer(maxSize, properties, batchSize, autoResize);
maxSize的默認值是1500。所以,如果需要包含更多的精靈,那就把這個值設爲更大的數字。 properties參數是一個對象,對象包含5個需要設置的布爾值:scale, position,rotation,uvs,alphaAndTint。position的默認值是true,其它4個參數的默認值是false。這意味着在ParticleContainer中,想要改變 scale,rotation,uvs,alphaAndTint,就不得不把這些屬性設置爲true,就像如下這樣:
let superFastSprites = new ParticleContainer(size,
{
rotation: true,
alphaAndtint: true,
scale: true,
uvs: true
}
);
但是,如果認爲不需要使用這些屬性,請將它們設置爲false可以最大限度地發揮性能。什麼是uvs屬性?僅當具有在動畫時更改其紋理的粒子時,纔將其設置爲true。(所有精靈的紋理也都必須在同一雪碧圖上才能起作用。) (注意:UV映射是3D圖形顯示術語,指的是被映射到3D表面上的紋理(圖像)的x和y座標。U是x軸,V是y軸。WebGL已經使用x,y和z用於3D空間定位,因此選擇U和V表示2D圖像紋理的x和y。)
在線示例。
19.pixi畫幾何圖形
(1).描述
使用圖像紋理是製作精靈最有用的方法之一,但是也有其自己的低級繪製工具。可以使用它們製作矩形,形狀,線,複雜的多邊形和文本。而且,幸運的是,它使用了與Canvas Drawing API幾乎相同的API,因此,如果已經熟悉canvas,就沒有什麼真正的新知識了。但是最大的好處是,與Canvas Drawing API不同,使用Pixi繪製的形狀由WebGL在GPU上渲染。Pixi可以利用所有未開發的性能。讓我們快速瀏覽一下如何製作一些基本形狀。下面是我們將要使用的代碼來創造的圖形。
(2).矩形
所有的形狀的初始化都是先創造一個Pixi的Graphics的類 (PIXI.Graphics)的實例。
let rectangle = new Graphics();
然後使用參數爲十六進制顏色代碼值的beginFill方法來設置矩形的填充顏色。以下是將其設置爲淺藍色的方法。
rectangle.beginFill(0x66CCFF);
如果想要給形狀設置一個輪廓,使用方法。以下爲給矩形設置一個4像素寬alpha值爲1的紅色輪廓的示例:
//第一個參數爲輪廓線寬度,第二個參數爲輪廓線顏色值,第三個參數爲alpha值
rectangle.lineStyle(4, 0xFF3300, 1);
使用drawRect方法來畫一個矩形,它的四個參數分別是x,y,width,height。
rectangle.drawRect(x, y, width, height);
使用endFill方法來結束繪製。就像Canvas Drawing 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的藍色的紅框矩形。
(3).圓形
使用drawCircle方法來創造一個圓。它的三個參數是x, y和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);
(4).橢圓形
作爲Canvas Drawing 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);
(5).圓角矩形
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);
(6).線段
從前面的例子我們已經知道使用lineStyle方法來繪製一條線段了。與Canvas Drawing API一樣,我們可以使用moveTo和lineTo方法來畫線段的開始和結束點。以下代碼畫了一條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值,就像sprites一樣,因此繪製它們之後,可以將它們放置在舞臺上的任何位置。
(7).多邊形
我們還可以使用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);
triangle.drawPolygon([
-32, 64,
32, 64,
0, 0
]);
triangle.endFill();
triangle.x = 180;
triangle.y = 22;
app.stage.addChild(triangle);
在線示例。
20.顯示文本
使用Text對象(PIXI.Text)在舞臺上顯示文本。在最簡單的形式中,可以這樣操作:
let message = new Text("Hello Pixi!");
app.stage.addChild(message);
這將在畫布上顯示單詞“Hello,Pixi”。Pixi的Text對象繼承自Sprite類,因此它們包含所有相同的屬性,例如x,y,width,height,alpha和rotation。 就像在其他精靈上一樣,在舞臺上放置文本並調整其大小。例如,可以使用position.set來設置消息的x和y位置,如下所示:
message.position.set(54, 96);
這將爲我們提供基本的,無樣式的文本。但是,如果想變得更時髦,請使用Pixi的TextStyle(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,
});
這將創建一個新的樣式對象,其中包含要使用的所有文本樣式。有關可以使用的所有樣式屬性的完整列表,請參見此處。 要將樣式應用於文本,請添加樣式對象作爲Text函數的第二個參數,如下所示:
let message = new Text("Hello Pixi!", style);
如果要在創建文本對象後更改其內容,可以使用text屬性。
message.text = "Text changed!";
如果要重新定義樣式屬性,可以使用style屬性。
message.style = {fill: "black", font: "16px PetMe64"};
Pixi通過使用Canvas Drawing API將文本呈現爲不可見的臨時畫布元素來製作文本對象。然後,它將畫布變成WebGL紋理,以便可以將其映射到精靈。這就是需要將文本的顏色包裹在字符串中的原因:這是Canvas Drawing API的顏色值。與任何畫布顏色值一樣,可以使用用於常見的顏色單詞,例如"red"或"green",也可以使用rgba,hsla或hex顏色模式。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的loader來加載位圖字體XML文件,就像加載JSON或圖像文件一樣。
在線示例。
21.碰撞檢測
(1).碰撞檢測介紹
我們現在知道了如何製作各種圖形對象,但是可以使用它們做什麼呢?一個有趣的事情是構建一個簡單的碰撞檢測系統。可以使用一個名爲hitTestRectangle的自定義函數,該函數檢查是否有兩個矩形Pixi精靈正在接觸。
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!"將由文本對象顯示。
我們已經看到了創建所有這些元素的所有代碼,以及使貓移動的鍵盤控制系統。唯一的新東西就是play函數內部使用hitTestRectangle來檢查碰撞的函數。
function play(delta) {
//使用cat精靈的速度屬性來移動
cat.x += cat.vx;
cat.y += cat.vy;
//檢查cat精靈與box精靈是否碰撞
if (hitTestRectangle(cat, box)) {
//如果碰撞則改變文本
//盒子顏色變成紅色
message.text = "hit!";
box.tint = 0xff3300;
} else {
//如果沒有碰撞重置文本與盒子顏色
message.text = "No collision...";
box.tint = 0xccff99;
}
}
由於play函數每秒被遊戲循環調用60次,因此該if語句會不斷檢查貓和盒子之間的碰撞。 如果hitTestRectangle返回的是true,則文本消息對象使用文本顯示"hit!":
message.text = "Hit!";
然後,通過將盒子的tint屬性設置爲十六進制的紅色值,將盒子的顏色從綠色更改爲紅色。
box.tint = 0xff3300;
如果沒有碰撞,則文本和盒子將保持其原始狀態:
message.text = "No collision...";
box.tint = 0xccff99;
這段代碼非常簡單,但是突然之間創建了一個似乎完全活躍的交互式世界。幾乎就像魔術!而且,也許令人驚訝的是,我們現在擁有開始使用Pixi製作遊戲所需的全部技能!
(2).碰撞檢測函數
但是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 occurring. 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;
};
在線示例。
22.實例學習:尋寶獵人小遊戲
到此爲止,我們現在已經擁有開始製作遊戲所需的所有技能。 什麼? 你不相信我嗎 讓我向你證明! 讓我們來看看如何製作一個簡單的對象收集和避免敵人的遊戲,稱爲《尋寶獵人》。
《尋寶獵人》是可以使用到目前爲止學到的工具製作的最簡單的完整遊戲之一的一個很好的例子。 使用鍵盤上的箭頭鍵可幫助探險家找到寶藏並將其帶到出口。六個Blob怪物在地牢壁之間上下移動,如果碰到了探索者,他將變成半透明, 並且右上角的血量進度條會縮小。如果所有血量都用光了,舞臺上會顯示“ You Lost!”; 如果探險家帶着寶藏到達出口,則顯示“ You Won!”。 儘管它是一個基本的原型,但《尋寶獵人》包含了您在大型遊戲中發現的大多數元素:紋理圖集圖形,交互性,碰撞以及多個遊戲場景。 讓我們瀏覽一下游戲的組合方式,以便可以將其用作自己的一款遊戲的起點。
(1).代碼結構
打開treasureHunter.html文件,你將會看到所有的代碼都在一個大的文件裏。 下面是一個關於如何組織所有代碼的概覽:
//創建pixi應用以及加載所有的紋理圖集的函數,就叫setup
function setup() {
//遊戲精靈的創建,開始遊戲狀態,開始遊戲循環
}
function gameLoop(delta) {
//運行遊戲循環
}
function play(delta) {
//所有的遊戲魔法都在這裏
}
function end() {
//遊戲最後所運行的代碼
}
//遊戲需要用到的工具函數:
//`keyboard`, `hitTestRectangle`, `contain`and `randomInt`
把這個當作你遊戲代碼的藍圖,讓我們看看每一部分是如何工作的。
(2).用setup函數初始化遊戲
加載紋理圖集圖像後,setup函數即會運行。它僅運行一次,並允許您爲遊戲執行一次性設置任務。 在這裏創建和初始化對象,精靈,遊戲場景,填充數據數組或解析加載的JSON遊戲數據的好地方。 以下是Treasure Hunter中setup函數及其執行任務的簡要視圖。
function setup() {
//創建遊戲開始場景分組
//創建門精靈
//創建玩家也就是探險者精靈
//創建寶箱精靈
//創造敵人
//創建血量進度條
//添加一些遊戲所需要的文本顯示
//創建遊戲結束場景分組
//分配玩家的鍵盤控制器
//設置遊戲狀態
state = play;
//開始遊戲循環
app.ticker.add(delta => gameLoop(delta));
}
代碼的最後兩行,state = play;和gameLoop可能是最重要的。 將gameLoop添加到Pixi的切換開關中可以打開遊戲引擎, 並在連續循環中調用play函數。但是,在研究其工作原理之前,讓我們先看看設置函數中的特定代碼是做什麼的。
a.創建遊戲場景
setup函數將創建兩個容器組,分別稱爲gameScene和gameOverScene。這些都添加到舞臺中。
gameScene = new Container();
app.stage.addChild(gameScene);
gameOverScene = new Container();
app.stage.addChild(gameOverScene);
屬於主遊戲的所有精靈都添加到gameScene組中。遊戲結束時應顯示在遊戲上方的文本將添加到gameOverScene組。
儘管它是在setup函數中創建的,但當遊戲首次啓動時gameOverScene不應該可見,因此其visible屬性被初始化爲false。
gameOverScene.visible = false;
我們將看到,當遊戲結束時,gameOverScene的visible屬性將設置爲true,以顯示出現在遊戲結束時的文本。
b.製作地牢,門,探險者與寶藏精靈
玩家,出口,寶箱以及地牢都是從紋理圖集中製作而來的精靈。十分重要的是,它們都被作爲gameScene的子精靈而添加。
//從紋理圖集中創建精靈
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操作起來更簡單。
c.製作泡泡怪精靈
6個blob怪物是循環創建的。每個blob都被賦予一個隨機的初始位置和速度。每個blob的垂直速度交替乘以1或-1,這就是導致每個blob沿與其相鄰方向相反的方向移動的原因。每個創建的blob怪物都會被推入稱爲blob的數組。
//泡泡怪數量
let numberOfBlobs = 6;
//泡泡怪水平位置值
let spacing = 48;
//泡泡怪偏移量
let xOffset = 150;
//泡泡怪速度
let speed = 2;
//泡泡怪移動方向
let direction = 1;
//一個數組存儲所有的泡泡怪
let blobs = [];
//開始創建泡泡怪
for (let i = 0; i < numberOfBlobs; i++) {
//創建一個泡泡怪
let blob = new Sprite(id["blob.png"]);
//根據`spacing`值將每個Blob水平隔開
//xOffset確定屏幕左側的點
//應在其中添加第一個Blob
let x = spacing * i + xOffset;
//給泡泡怪一個隨機的垂直方向上的位置
let y = randomInt(0, stage.height - blob.height);
//設置泡泡怪的位置
blob.x = x;
blob.y = y;
//設置泡泡怪的垂直速度。 方向將爲1或
//`-1。“ 1”表示敵人將向下移動,“-1”表示泡泡怪將
//提升。將“方向”與“速度”相乘即可確定泡泡怪
//垂直方向
blob.vy = speed * direction;
//下一個泡泡怪方向相反
direction *= -1;
//將泡泡怪添加到數組中
blobs.push(blob);
//將泡泡怪添加到gameScene分組中
gameScene.addChild(blob);
}
d.製作血量進度條
當我們在玩尋寶獵人的時候,我想應該會發現,當我們的探險者觸碰到任何一個敵人的時候,屏幕右上角的血量進度條的寬度都會減少。那麼這個血量進度條是如何製作的呢?它僅僅只是兩個相同位置重疊的矩形:一個黑色的矩形在後面,一個紅色的矩形在前面。它們都被分在healthBar分組中。healthBar被添加到gameScene分組中,然後在舞臺上被定位。
//創建血量進度條
healthBar = new PIXI.Container();
healthBar.position.set(stage.width - 170, 4)
gameScene.addChild(healthBar);
//創建黑色的矩形
let innerBar = new PIXI.Graphics();
innerBar.beginFill(0x000000);
innerBar.drawRect(0, 0, 128, 8);
innerBar.endFill();
healthBar.addChild(innerBar);
//創建紅色的矩形
let outerBar = new PIXI.Graphics();
outerBar.beginFill(0xFF3300);
outerBar.drawRect(0, 0, 128, 8);
outerBar.endFill();
healthBar.addChild(outerBar);
healthBar.outer = outerBar;
我們已經看到一個被叫做outer的屬性被添加到healthBar中。它僅僅引用outerBar(紅色矩形),以便以後訪問時很方便。
healthBar.outer = outerBar;
我們不必這樣做;但是,爲什麼不呢!這意味着,如果我們想控制紅色outerBar的寬度,則可以編寫一些如下所示的平滑代碼:
healthBar.outer.width = 30;
那很整潔而且可讀,所以我們會保留它!
e.製作文本提示
遊戲結束時,根據遊戲的結果,一些文本顯示“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);
(3).開始遊戲
所有使精靈移動的遊戲邏輯和代碼都發生在play函數內部,該函數連續循環運行。這是play函數的概述:
function play(delta) {
//移動探險者並將其包含在地牢中
//移動泡泡怪
//檢測泡泡怪與探險者的碰撞
//檢測探險者與寶箱的碰撞
//檢測寶箱與出口的碰撞
//決定遊戲是贏還是輸
//遊戲結束時,改變遊戲的狀態爲end
}
讓我們找出所有這些功能的工作方式。
(4).移動探險者
探險者是使用鍵盤控制的,執行該操作的代碼與先前學習的鍵盤控制代碼非常相似。鍵盤對象會修改探險者的速度,並將該速度添加到play函數中探險者的位置。
explorer.x += explorer.vx;
explorer.y += explorer.vy;
a.運動範圍
但是,新功能是探險者的動作被包含在地牢的牆壁內。綠色輪廓線顯示了探險者運動的極限。
這是在名爲contain的自定義函數的幫助下完成的。
contain(explorer, {x: 28, y: 10, width: 488, height: 480});
contain包含2個參數。第一個參數是你想要被包含的精靈,第二個參數則是任意的一個對象,包含x,y,width,height屬性,爲了定義一個矩形區域。在這個例子中,contain對象定義了一個僅比舞臺稍微偏移且小於舞臺的區域,它與地牢牆的尺寸所匹配。
以下是完成這些工作的contain函數。該函數檢查精靈是否已超出contain對象的邊界。 如果超出了,則代碼將精靈移回該邊界。contain函數還會返回碰撞變量,其值取決於"top","right","bottom","left",具體取決於擊中邊界的哪一側。(如果精靈沒有碰到任何邊界,則碰撞將是不確定的。)
function contain(sprite, container) {
let collision = undefined;
//左
if (sprite.x < container.x) {
sprite.x = container.x;
collision = "left";
}
//上
if (sprite.y < container.y) {
sprite.y = container.y;
collision = "top";
}
//右
if (sprite.x + sprite.width > container.width) {
sprite.x = container.width - sprite.width;
collision = "right";
}
//下
if (sprite.y + sprite.height > container.height) {
sprite.y = container.height - sprite.height;
collision = "bottom";
}
//返回collision的值
return collision;
}
你將看到如何在前面的代碼中使用碰撞返回值,以使Blob怪物在上層和下層地下城牆之間來回反彈。
(4).移動怪物
play函數還可以移動Blob怪物,將它們保留在地牢壁中,並檢查每個怪物是否與玩家發生碰撞。如果Blob撞到地牢的頂壁或底壁,則其方向會相反。所有這些都是在forEach循環的幫助下完成的,該循環遍歷每幀Blobs數組中的每個Blob精靈。
blobs.forEach(function(blob) {
//移動泡泡怪
blob.y += blob.vy;
//檢查泡泡怪的屏幕邊界
let blobHitsWall = contain(blob, {x: 28, y: 10, width: 488, height: 480});
//如果泡泡怪撞到舞臺的頂部或者底部,則方向反轉
//它的方向
if (blobHitsWall === "top" || blobHitsWall === "bottom") {
blob.vy *= -1;
}
//碰撞檢測如果任意敵人觸碰到探險者
//將探險者的explorerHit值設置爲true
if(hitTestRectangle(explorer, blob)) {
explorerHit = true;
}
});
我們可以在上面的代碼中看到contain函數的返回值如何用於使blob從牆反彈。名爲blobHitsWall的變量用於捕獲返回值:
let blobHitsWall = contain(blob, {x: 28, y: 10, width: 488, height: 480});
blobHitsWall通常是undefined(未定義)的。但是,如果blob碰到了頂壁,則blobHitsWall的值將爲“top”。 如果blob碰到底壁,則blobHitsWall的值將爲“bottom”。如果以上兩種情況均成立,則可以通過反轉blob的速度來反轉blob的方向。以下是執行此操作的代碼:
if (blobHitsWall === "top" || blobHitsWall === "bottom") {
//通過改變速度爲負值來反轉方向
blob.vy *= -1;
}
將blob的vy(垂直速度)值乘以-1將翻轉其運動方向。
(5).檢測碰撞
前面循環中的代碼使用hitTestRectangle函數來確定是否有任何敵人觸摸了探險者。
if(hitTestRectangle(explorer, blob)) {
explorerHit = true;
}
如果hitTestRectangle返回的是true。也就意味着會發生一次碰撞並且explorerHit變量的值會是true。如果explorerHit的值是true,play函數將會使探險者變成半透明,並且血量進度條的寬度減少1像素。(具體減少多少依據每個人自己定義。)。
if(explorerHit) {
//使探險者變成半透明
explorer.alpha = 0.5;
//減少血量進度條的寬度
healthBar.outer.width -= 1;
} else {
//使探險者完全透明,如果不能再被撞擊
explorer.alpha = 1;
}
如果explorerHit爲false,則將explorer的alpha屬性保持爲1,這使其完全不透明。play函數還檢查寶箱和探險者之間是否發生碰撞。如果有發生碰撞,寶藏將設置到探險者的位置,並稍有偏移。這使其看起來像探險家正在攜帶寶藏。
以下是完成這個工作的代碼:
if (hitTestRectangle(explorer, treasure)) {
//8的數字還可以再大一點點
treasure.x = explorer.x + 8;
treasure.y = explorer.y + 8;
}
(6).到達出口並結束遊戲
有兩種方式會讓遊戲結束:探險者攜帶寶箱併到達了出口就表示你贏了,或者就是你的血量進度條沒有了那就表示你失敗了。爲了贏得遊戲探險者僅僅只需要觸碰到出口,如果發生了這種情況。那麼將遊戲的狀態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次的速度不斷更新稱爲狀態的函數。這是執行此操作的gameLoop:
function gameLoop(delta){
//更新當前的遊戲狀態
state(delta);
}
我們還將記住,我們最初將狀態值設置爲play,這就是爲什麼play函數循環運行的原因。通過將狀態設置爲end,我們告訴代碼我們想要另一個函數,稱爲end的循環運行。在更大的遊戲中,還可以具有tileScene狀態,以及每個遊戲級別的狀態,例如leveOne,levelTwo和levelThree。
end函數是什麼?以下便是:
function end() {
//遊戲場景不顯示,遊戲結束場景顯示
gameScene.visible = false;
gameOverScene.visible = true;
}
它只是翻轉遊戲場景的可見性。這是隱藏gameScene並在遊戲結束時顯示gameOverScene的內容。 這是一個非常簡單的示例,說明了如何切換遊戲狀態,但是可以在遊戲中擁有任意數量的遊戲狀態,並根據需要填充儘可能多的代碼。只需將state的值更改爲要在循環中運行的任何函數。 而這正是尋寶獵人的全部!只需多做一些工作,就可以將這個簡單的原型變成完整的遊戲-試試吧!
在線示例。
23.更多關於精靈的知識
到目前爲止,我們已經學習瞭如何使用許多有用的精靈屬性,例如x,y,visible和rotation,這些屬性使我們可以大量控制精靈的位置和外觀。但是Pixi Sprites還具有許多更有趣的有用屬性。這是完整列表。 Pixi的類繼承系統如何運作?(什麼是類,什麼是繼承?單擊此鏈接以查找。)Pixi的sprites建立在遵循此鏈的繼承模型上:
DisplayObject > Container > Sprite
繼承只是意味着鏈中後面的類使用鏈中前面的類的屬性和方法。這意味着,即使Sprite是鏈中的最後一個類,它除了具有自己的獨特屬性外,還具有與DisplayObject和Container相同的所有屬性。 最基本的類是DisplayObject。任何DisplayObject都可以在舞臺上呈現。容器是繼承鏈中的下一個類。它允許DisplayObject充當其他DisplayObject的容器。排名第三的是Sprite類。精靈既可以顯示在舞臺上,也可以作爲其他精靈的容器。
(PS:本文基於官方教程而翻譯並加入了個人的理解以及示例,不喜勿噴。本文總結在本人的個人網站上)。