# LearningPixi **Repository Path**: ForestLiu/LearningPixi ## Basic Information - **Project Name**: LearningPixi - **Description**: LearningPixi中文版 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 5 - **Created**: 2025-04-05 - **Last Updated**: 2025-04-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README Pixi教程 ============= 基于官方教程翻译;水平有限,如有错误欢迎提PR,转载请注明出处。翻译者为[htkz](https://github.com/htkz)(完成了用 Pixi 绘制几何图形 和 显示文本 章节)和[zainking](https://github.com/ZainKing)(完成了其他所有章节) 另感谢[htkz](https://github.com/explooosion)、[NearZXH](https://github.com/NearZXH)以及[HHHHhgqcdxhg](https://github.com/HHHHhgqcdxhg)对错误及错别字等做出的订正。 这个教程将要一步步介绍怎么用[Pixi](https://github.com/pixijs/pixi.js)做游戏或者交互式媒体。这个教程已经升级到 **[Pixi v4.5.5](https://github.com/pixijs/pixi.js/releases/tag/v4.5.5)**。如果你喜欢这个教程,[你一定也喜欢这本书,它比这个教程多了80%的内容](http://www.springer.com/us/book/9781484210956)。 ### 目录: 1. [介绍](#introduction) 2. [安装](#settingup) 1. [安装 Pixi](#installingpixi) 3. [创建舞台(stage)和画布(renderer)](#application) 4. [Pixi 精灵](#sprites) 5. [把图像加载进纹理缓存](#loading) 6. [显示精灵(sprite)](#displaying) 1. [使用别名](#usingaliases) 2. [一些关于加载的其他知识](#alittlemoreaboutloadingthings) 1. [使用普通的javaScript Img对象或canvas创建一个精灵](#makeaspritefromanordinaryjavascriptimageobject) 2. [给已经加载的文件设定一个名字](#assigninganametoaloadingfile) 3. [监视加载进程](#monitoringloadprogress) 4. [一些关于Pixi的加载器的其他知识](#moreaboutpixisloader) 7. [定位精灵](#positioning) 8. [大小和比例](#sizenscale) 9. [角度](#rotation) 10. [从精灵图(雪碧图)中获取精灵](#tileset) 11. [使用一个纹理贴图集](#textureatlas) 12. [加载纹理贴图集](#loadingatlas) 13. [从一个纹理贴图集创建精灵](#creating-sprites-from-a-loaded-texture-atlas) 14. [移动精灵](#movingsprites) 15. [使用速度属性](#velocity) 16. [游戏状态](#gamestates) 17. [键盘响应](#keyboard) 18. [将精灵分组](#grouping) 1. [局部位置和全局位置](#localnglobal) 2. [使用 ParticleContainer 分组精灵](#spritebatch) 19. [用 Pixi 绘制几何图形](#graphic) 1. [矩形](#rectangles) 2. [圆形](#circles) 3. [椭圆](#ellipses) 4. [圆角矩形](#rounded-rectangles) 5. [线](#lines) 6. [多边形](#polygons) 20. [显示文本](#text) 21. [碰撞检测](#collision) 1. [一个 hitTestRectangle 函数](#the-hittestrectangle-function) 22. [实例学习: 宝物猎人](#casestudy) 1. [用 setup 函数初始化游戏](#initialize) 1. [创建游戏场景](#gamescene) 2. [创建地牢,门,猎人和宝箱](#makingdungon) 3. [创建泡泡怪(这个怪物好萌)](#makingblob) 4. [创建血条](#healthbar) 5. [创建提示文本](#message) 2. [开始游戏](#playing) 3. [移动猎人](#movingexplorer) 1. [限制移动范围](#containingmovement) 4. [移动泡泡怪们](#movingmonsters) 5. [碰撞检测](#checkingcollisions) 6. [处理到达出口和结束游戏](#reachingexit) 23. [一些关于精灵的其他知识](#spriteproperties) 24. [展望未来](#takingitfurther)
i.[Hexi](#hexi)
ii.[BabylonJS](#babylonjs)
25. [支持这个工程](#supportingthisproject) 介绍 ------------ Pixi是一个超快的2D渲染引擎。这意味着什么呢?这意味着它会帮助你用JavaScript或者其他HTML5技术来显示媒体,创建动画或管理交互式图像,从而制作一个游戏或应用。它拥有语义化的,简洁的API接口并且加入了一些非常有用的特性。比如支持纹理贴图集和为精灵(交互式图像)提供了一个简单的动画系统。它也提供了一个完备的场景图,你可以在精灵图层里面创建另一个精灵,当然也可以让精灵响应你的鼠标或触摸事件。最重要的的是,Pixi没有妨碍你的编程方式,你可以自己选择使用多少它的功能,你可以遵循你自己的编码风格,或让Pixi与其他有用的框架无缝集成。 Pixi的API事实上比起久经沙场又老旧的Macromedia/Adobe Flash API要精致。如果你是一个Flash开发者,将会对这样的API感觉更好。其他的同类渲染框架(比如CreateJS,Starling, Sparrow 和 Apple’s SpriteKit.)也在使用类似的API。Pixi API的优势在于它是通用的:它不是一个游戏引擎。这是一个优势,因为它给了你所有的自由去做任何你想做的事,甚至用它可以写成你自己的游戏引擎。 在这个教程里,你将会明白怎样用Pixi的强大的图片渲染能力和场景图技术来和做一个游戏联系起来。但是Pixi不仅仅能做游戏 —— 你能用这个技术去创建任何交互式媒体应用。这甚至意味着手机应用。 你在开始这个教程之前需要知道什么呢? 你需要一个对于HTML和JavaScript大致的了解。你没必要成为这方面的专家才能开始,即使一个野心勃勃的初学者也可以开始学习。这本书就是一个学习的好地方: [Foundation Game Design with HTML5 and JavaScript](http://www.apress.com/9781430247166) 我知道这本书是最好的,因为这本书是我写的! 这里有一些好的代码来帮助你开始: [Khan Academy: Computer Programming](http://www.khanacademy.org/computing/cs) [Code Academy: JavaScript](http://www.codecademy.com/tracks/javascript) 选择一个属于你的最好的学习方式吧! 所以,明白了么? 你知道JavaScript的变量,函数,数组和对象怎么使用么?你知道[JSON 数据文件](http://www.copterlabs.com/blog/json-what-it-is-how-it-works-how-to-use-it/)是什么么? 你用过 [Canvas 绘图 API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Drawing_graphics_with_canvas)么? 为了使用Pixi,你也需要在你项目的根目录运行一个web服务器,你知道什么是web服务器,怎么在你的项目文件夹里面运行它么?最好的方式是使用[node.js](http://nodejs.org) 并且去用命令行安装[http-server](https://github.com/nodeapps/http-server). 无论如何,你需要习惯和Unix命令行一起工作。你可以[在这个视频中](https://www.youtube.com/watch?feature=player_embedded&v=cX9ASUE3YAQ)去学习怎样使用 Unix当你完成时,继续去学习 [这个视频](https://www.youtube.com/watch?v=INk0ATBbclc).你应该学会怎样用Unix,这是一个很有趣和简单的和电脑交互的方式,并且仅仅需要两个小时。 如果你真的不想用命令行的方式,就尝试下 Mongoose webserver: [Mongoose](http://cesanta.com/mongoose.shtml) 或者来使用[Brackets text editor](http://brackets.io)这个令人惊艳的代码编辑器。他会在你点击那个“闪电按钮”的时候自动启动web服务器和浏览器。 现在,如果你觉得你准备好了了,开始吧! (给读者的小提示:这是一个 *交互式的文档*.如果你有关于特殊细节的任何问题或需要任何澄清都可以创建一个GitHub工程 **issue** ,我会对这个文档更新更多信息。) 安装 ---------- 在你开始写任何代码之前,给你的工程创建一个目录,并且在根目录下运行一个web服务器。如果你不这么做,Pixi不会工作的。 现在,你需要去安装Pixi。 ### 安装 Pixi 这个教程使用的版本是 **v4.5.5** 你可以选择使用 [Pixi v4.5.5的发布页面](https://github.com/pixijs/pixi.js/releases/tag/v4.5.5)`pixi`文件夹下的`pixi.min.js`文件,或者从[Pixi的主要发布页面](https://github.com/pixijs/pixi.js/releases)中获取最新版本。 这个文件就是你使用Pixi唯一需要的文件,你可以忽视所有这个工程的其他文件,**你不需要他们**。 现在,创建一个基础的HTML页面,用一个` ``` 这是你用来链接Pixi和测试它是否工作的基础页面。(这里假设 `pixi.min.js`在一个叫做`pixi`的子文件夹中): ```html Hello World ``` 如果Pixi连接成功,一些这样的东西会在你的浏览器控制台里显示: ``` PixiJS 4.4.5 - * canvas * http://www.pixijs.com/ ♥♥♥ ``` 创建Pixi应用和 `舞台` ------------------------------- 现在你可以开始使用Pixi! 但是怎么用? 第一步就是去创建一个可以显示图片的矩形显示区。Pixi拥有一个`Pixi应用`对象来帮助你创建它。它会自动创建一个``HTML标签并且计算出怎么去让你的图片在这个标签中显示。你现在需要创建一个特殊的Pixi`容器`对象,他被称作`舞台`。正如你所见,这个`舞台`对象将会被当作根容器而使用,它将包裹所有你想用Pixi显示的东西。 这里是你需要创建一个名叫`app`的Pixi应用对象和一个`舞台`的必要的代码。这些代码需要在你的HTML文档中以` ``` 你可以看见所有的泡泡怪都用一个`for`循环被创建了,每一个泡泡怪都有一个独一无二的`x`坐标,像是下面这样: ```js let x = spacing * i + xOffset; blob.x = x; ``` `spacing`变量的值是48,`xOffset`的值是150。这意味着第一个`blob`怪的位置的`x`坐标将会是150。这个偏移使得泡泡怪离舞台左边的距离有150个像素。每一个泡泡怪都有个48像素的空余,也就是说每一个泡泡怪都会比在循环之中前一个创建的泡泡怪的位置的`x`坐标多出48像素以上的增量。它使得泡泡怪们相互间隔,从地牢地板的左边排向右边。 每一个`blob`也被赋予了一个随机的`y`坐标,这里是处理这件事的代码: ```js let y = randomInt(0, stage.height - blob.height); blob.y = y; ``` 泡泡怪的`y`坐标将会从0到512之间随机取值,它的变量名是`stage.height`。它的值是利用`randomInt`函数来得到的。`randomInt`返回一个由你定义范围的随机数。 ```js randomInt(lowestNumber, highestNumber) ``` 这意味着如果你想要一个1到10之间的随机数,你可以这样得到它: ```js let randomNumber = randomInt(1, 10); ``` 这是`randomInt`方法的定义: ```js function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } ``` `randomInt`是一个很好的用来做游戏的工具函数,我经常用他。 移动精灵 -------------- 现在你知道了如何展示精灵,但是让它们移动呢?很简单:使用Pixi的`ticker`。这被称为 **游戏循环** 。任何在游戏循环里的代码都会1秒更新60次。你可以用下面的代码让 `cat` 精灵以每帧1像素的速率移动。 ```js 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](/examples/images/screenshots/15.png) 因为每当开始 `游戏循环` 的时候,都会为这只猫增加1像素的x轴位移。 ``` cat.x += 1; ``` 每一个你放进Pixi的`ticker`的函数都会每秒被执行60次。你可以看见函数里面提供了一个`delta`的内容,他是什么呢? `delta`的值代表帧的部分的延迟。你可以把它添加到cat的位置,让cat的速度和帧率无关。下面是代码: ```js cat.x += 1 + delta; ``` 是否加进去这个`delta`的值其实是一种审美的选择。它往往只在你的动画没法跟上60帧的速率时候出现(比如你的游戏运行在很老旧的机器上)。教程里面不会用到`delta`变量,但是如果你想用就尽情的用吧。 你也没必要非得用Pixi的ticker来创建游戏循环。如果你喜欢,也可以用`requestAnimationFrame`像这样创建: ```js 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 文件中找到这段代码 - 这是全部的代码: ```js //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轴(垂直)的速度和方向。 他们可以直接更新速度变量并且给精灵设定这些速度值。这是一个用来让你更方便的更新交互式动画的额外的模块。 第一步是给你的精灵创建`vx`和`vy`属性,然后给他们初始值。 ```js cat.vx = 0; cat.vy = 0; ``` 给`vx`和`vy`设置为0表示精灵静止。 接下来,在游戏循环中,更新`vx`和`vy`为你想让精灵移动的速度值。然后把这些值赋给精灵的`x`和`y`属性。下面的代码讲明了你如何利用该技术让cat能够每帧向右下方移动一个像素: ```js 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](/examples/images/screenshots/16.png) 如果你想让猫往不同的方向移动怎么办?可以把它的 `vx` 赋值为 -1让猫向左移动。可以把它的 `vy` 赋值为 -1让猫向上移动。为了让猫移动的更快一点,把值设的更大一点,像3, 5, -2, 或者 -4。 你会在前面看到如何通过利用`vx`和`vy`的速度值来模块化精灵的速度,它对游戏的键盘和鼠标控制系统很有帮助,而且更容易实现物理模拟。 游戏状态 ----------- 作为一种代码风格,也为了帮你模块你的代码,我推荐在游戏循环里像这样组织你的代码: ```js //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次。 下面的代码告诉你如何用这个新模式来重构上一个例子的代码: ```js //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](http://www.amazon.com/Electric-Psychedelic-Sitar-Headswirlers-1-5/dp/B004HZ14VS)! 但是,不要害怕,花几分钟在脑海中想一遍这些函数是如何联系在一起的。正如你将在下面看到的,结构化你的游戏循环代码,会使得切换游戏场景和关卡这种操作变得更简单。 键盘移动 ----------------- 只需再做一点微小的工作,你就可以建立一个通过键盘控制精灵移动的简单系统。为了简化你的代码,我建议你用一个名为`keyboard`的自定义函数来监听和捕捉键盘事件。 ```js 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`函数用起来很容易,可以像这样创建一个新的键盘对象: ```js let keyObject = keyboard(asciiKeyCodeNumber); ``` 这个函数只接受一个参数就是键盘对应的ASCII键值数,也就是你想监听的键盘按键。 这是[键盘键ASCII值列表](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode). 然后给键盘对象赋值`press`和`release`方法: ```js keyObject.press = () => { //key object pressed }; keyObject.release = () => { //key object released }; ``` 键盘对象也有 `isDown` 和 `isUp` 的布尔值属性,你可以用它们来检查每个按键的状态。 在`examples`文件夹里看一下`keyboardMovement.html`文件是怎么用`keyboard`函数的,利用键盘的方向键去控制精灵图。运行它,然后用上下左右按键去让猫在舞台上移动。 ![Keyboard movement](/examples/images/screenshots/17.png) 这里是代码: ```js //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`,它可以帮你做这些工作。让我们弄清楚它是怎么工作的。 想象一下你想展示三个精灵:一只猫,一只刺猬和一只老虎。创建它们,然后设置它们的位置 - *但是不要把它们添加到舞台上*。 ```js //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`容器像这样去把他们聚合在一起: ```js let animals = new Container(); ``` 然后用 `addChild` 去把精灵图 *添加到分组中* 。 ```js animals.addChild(cat); animals.addChild(hedgehog); animals.addChild(tiger); ``` 最后把分组添加到舞台上。 ```js app.stage.addChild(animals); ``` (你知道的,`stage`对象也是一个`Container`。它是所有Pixi精灵的根容器。) 这就是上面代码的效果: ![Grouping sprites](/examples/images/screenshots/18.png) 你是看不到这个包含精灵图的`animals`分组的。它仅仅是个容器而已。 ![Grouping sprites](/examples/images/screenshots/19.png) 不过你现在可以像对待一个单一单元一样对待`animals`分组。你可以把`Container`当作是一个特殊类型的不包含任何纹理的精灵。 如果你需要获取`animals`包含的所有子精灵,你可以用它的`children`数组获取。 ``` console.log(animals.children) //Displays: Array [Object, Object, Object] ``` 这告诉你`animals`有三个子精灵。 因为`animals`分组跟其他精灵一样,你可以改变它的`x`和`y`的值,`alpha`, `scale`和其他精灵的属性。所有你改变了的父容器的属性值,都会改变它的子精灵的相应属性。所以如果你设置分组的`x`和`y`的位置,所有的子精灵都会相对于分组的左上角重新定位。如果你设置了 `animals`的`x`和`y`的位置为64会发生什么呢? ``` animals.position.set(64, 64); ``` 整个分组的精灵都会向右和向下移动64像素。 ![Grouping sprites](/examples/images/screenshots/20.png) `animals`分组也有它自己的尺寸,它是以包含的精灵所占的区域计算出来的。你可以像这样来获取`width`和`height`的值: ```js console.log(animals.width); //Displays: 112 console.log(animals.height); //Displays: 112 ``` ![Group width and height](/examples/images/screenshots/21.png) 如果你改变了分组的宽和高会发生什么呢? ```js animals.width = 200; animals.height = 200; ``` 所有的孩子精灵都会缩放到刚才你设定的那个值。 ![Group width and height](/examples/images/screenshots/22.png) 如果你喜欢,你可以在一个 `Container` 里嵌套许多其他`Container`,如果你需要,完全可以创建一个更深的层次。然而,一个 `DisplayObject` (像 `Sprite` 或者其他 `Container`)只能一次属于一个父级。如果你用 `addChild` 让一个精灵成为其他精灵的孩子。Pixi会自动移除它当前的父级,这是一个不用你操心的有用的管理方式。 ### 局部位置和全局位置 当你往一个`Container`添加一个精灵时,它的`x`和`y`的位置是 *相对于分组的左上角* 的。这是精灵的局部位置,举个例子,你认为这个猫在这张图的哪个位置? ![Grouping sprites](/examples/images/screenshots/20.png) 让我们看看: ``` 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...}; ``` 上面给你返回了`x`和`y`的值为80。这正是猫相对于舞台左上角的相对位置,也就是全局位置。 如果你想知道一个精灵的全局位置,但是不知道精灵的父容器怎么办?每个精灵图有一个属性叫`parent` 能告诉你精灵的父级是什么。在上面的例子中,猫的父级是 `animals`。这意味着你可以像如下代码一样得到猫的全局位置: ``` cat.parent.toGlobal(cat.position); ``` 即使你不知道猫的当前父级是谁,上面的代码依然能够正确工作。 这还有一种方式能够计算出全局位置!而且,它实际上最好的方式,所以听好啦!如果你想知道精灵到canvas左上角的距离,但是不知道或者不关心精灵的父亲是谁,用`getGlobalPosition`方法。这里展示如何用它来找到老虎的全局位置: ```js tiger.getGlobalPosition().x tiger.getGlobalPosition().y ``` 它会给你返回`x`和`y`的值为128。 特别的是,`getGlobalPosition`是高精度的:当精灵的局部位置改变的同时,它会返回给你精确的全局位置。我曾要求Pixi开发团队添加这个特殊的特性,以便于开发精确的碰撞检测游戏。(谢谢Matt和团队真的把他加上去了!) 如果你想转换全局位置为局部位置怎么办?你可以用`toLocal`方法。它的工作方式类似,但是通常是这种通用的格式: ```js sprite.toLocal(sprite.position, anyOtherSprite) ``` 用 `toLocal` 找到一个精灵和其他任何一个精灵之间的距离。这段代码告诉你如何获取老虎的相对于猫头鹰的局部位置。 ```js tiger.toLocal(tiger.position, hedgehog).x tiger.toLocal(tiger.position, hedgehog).y ``` 上面的代码会返回给你一个32的`x`值和一个32的`y`值。你可以在例子中看到老虎的左上角和猫头鹰的左上角距离32像素。 ### 使用 ParticleContainer 分组精灵 Pixi有一个额外的,高性能的方式去分组精灵的方法称作:`ParticleContainer`(`PIXI.ParticleContainer`)。任何在`ParticleContainer` 里的精灵都会比在一个普通的`Container`的渲染速度快2到5倍。这是用于提升游戏性能的一个很棒的方法。 可以像这样创建 ParticleContainer : ```js let superFastSprites = new PIXI.particles.ParticleContainer(); ``` 然后用 `addChild` 去往里添加精灵,就像往普通的 `Container`添加一样。 如果你决定用`ParticleContainer`你必须做出一些妥协。在 `ParticleContainer` 里的精灵图只有一小部分基本属性:`x`, `y`, `width`, `height`, `scale`, `pivot`, `alpha`, `visible` - 就这么多。而且,它包含的精灵不能再继续嵌套自己的孩子精灵。 `ParticleContainer` 也不能用Pixi的先进的视觉效果像过滤器和混合模式。每个` ParticleContainer` 只能用一个纹理(所以如果你想让精灵有不同的表现方式你将不得不更换雪碧图)。但是为了得到巨大的性能提升,这些妥协通常是值得的。你可以在同一个项目中同时用 `Container` 和 `ParticleContainer`,然后微调一下你自己的优化。 为什么在 `Particle Container` 的精灵图这么快呢?因为精灵的位置是直接在GPU上计算的。Pixi开发团队正在努力让尽可能多的雪碧图在GPU上处理,所以很有可能你用的最新版的Pixi的 `ParticleContainer` 的特性一定比我现在在这儿描述的特性多得多。查看[当前 `ParticleContainer` 文档](http://pixijs.download/release/docs/PIXI.particles.ParticleContainer.html)以获取更多信息。 当你创建一个 `ParticleContainer`,有四个参数可以传递, `size`, `properties`, `batchSize` 和 `autoResize`。 ```js let superFastSprites = new ParticleContainer(maxSize, properties, batchSize, autoResize); ``` 默认的`maxSize`是 1,500。所以,如果你需要包裹更多的精灵,把它设置为更高的数字。配置参数是一个拥有五个布尔值的对象:`scale`, `position`, `rotation`, `uvs` 和 `alpha`。默认的值是 `position` 为 `true`,其他都为 `false`。这意味着如果你想在 `ParticleContainer `改变精灵的`rotation`, `scale`, `alpha`, 或者 `uvs`,你得先把这些属性设置为 `true`,像这样: ```js let superFastSprites = new ParticleContainer( size, { rotation: true, alphaAndtint: true, scale: true, uvs: true } ); ``` 但是,如果你感觉你不需要用这些属性,就保持它们为 `false` 以实现出更好的性能。 `uvs` 是什么呢?只有当它们在动画时需要改变它们纹理子图像的时候你需要设置它为 `true` 。(想让它工作,所有的精灵纹理需要在同一张雪碧图上。) (注意:**UV mapping** 是一个3D图表展示术语,它指纹理(图片)准备映射到三维表面的`x`和`y`的坐标。`U` 是 `x` 轴, `V` 是 `y` 轴。WebGL用 `x`, `y` 和 `z` 来进行三维空间定位,所以 `U` 和 `V` 被选为表示2D图片纹理的 `x` 和 `y` 。) (我真不知道最后两个参数干什么用的,就是`batchSize` 和 `autoResize`,如果你知道,就赶紧提个Issue吧!) 用Pixi绘制几何图形 ------------------------- 使用图片纹理是制作精灵最有效的方式之一,但是Pixi也提供了自己低级的绘画工具。你可以使用它们来创造矩形、线段、复杂的多边形以及文本。并且它使用和[Canvas Drawing API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Drawing_graphics_with_canvas)几乎一致的api,所以如果你熟悉canvas的话,那么几乎没有什么新东西需要学习。当然另一个巨大的优势在于,不同于Canvas的绘画api,你使用Pixi绘制的图形是通过WebGL在GPU上渲染的。Pixi能够让你获得所有未触碰到的性能。让我们简单看一下如何创造一些基本图形。下面是我们将要使用前面代码来创造的图形。 ![Graphic primitives](/examples/images/screenshots/23.png) ### 矩形 所有的形状的初始化都是先创造一个Pixi的`Graphics`的类 (`PIXI.Graphics`)的实例。 ```js let rectangle = new Graphics(); ``` 调用`beginFill`和一个16进制的颜色值来设置矩形的填充颜色。下面展示如何设置颜色为淡蓝色。 ```js rectangle.beginFill(0x66CCFF); ``` 如果你想要给图形设置一个轮廓,使用`lineStyle`方法。下面展示如何给矩形设置一个4像素宽`alpha`值为1的红色轮廓 ```js rectangle.lineStyle(4, 0xFF3300, 1); ``` 调用`drawRect`方法来画一个矩形。它的四个参数是`x`, `y`, `width` 和 `height`。 ```js rectangle.drawRect(x, y, width, height); ``` 调用`endFill`结束绘制。 ```js rectangle.endFill(); ``` 它看起来就像Canvas的绘画api一样!下面是绘制一个矩形涉及到的所有代码,调整它的位置并且把它添加到舞台吧。 ```js 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`方法来创造一个圆。它的三个参数是`x`, `y` 和 `radius`。 ```js drawCircle(x, y, radius) ``` 不同于矩形和精灵,一个圆形的x和y坐标也是它自身的圆点。下面展示如何创造半径32像素的紫色圆形。 ```js 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`来绘制椭圆。 ```js drawEllipse(x, y, width, height); ``` x/y坐标位置决定了椭圆的左上角(想象椭圆被一个不可见的矩形边界盒包围着-盒的左上角代表了椭圆x/y的锚点位置)。下面是50像素宽20像素高的黄色椭圆。 ```js 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`是单位为像素的数字,它代表矩形的圆角应该有多圆。 ```js drawRoundedRect(x, y, width, height, cornerRadius) ``` 下面展示如何创建一个圆角半径为10的圆角矩形。 ```js 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像素宽的白色对角线。 ```js 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坐标。 ```js let path = [ point1X, point1Y, point2X, point2Y, point3X, point3Y ]; graphicsObject.drawPolygon(path); ``` `drawPolygon`会将上面三个点连接起来创造图形。下面是如何使用`drawPolygon`来连接三条线从而创建一个红底蓝边的三角形。我们将三角形绘制在(0,0)的位置上,之后通过调整它的`x` 和 `y`属性来移动它在舞台上的位置。 ```js 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`)在舞台上展示文本。简单来说,你可以这样使用它: ```js let message = new Text("Hello Pixi!"); app.stage.addChild(message); ``` 这将会在画布上展示文本“Hello, Pixi”。Pixi的文本对象继承自`Sprite`类,所以它包含了所有相同的属性,像`x`, `y`, `width`, `height`, `alpha`, 和 `rotation`。你可以像处理其他精灵一样在舞台上定位或调整文本。例如,你可以像下面这样使用`position.set`来设置`message`的`x`和`y`位置: ```js message.position.set(54, 96); ``` ![Displaying text](/examples/images/screenshots/24.png) 这样你会得到基础的未加修饰的文本。但是如果你想要更绚丽的文字,使用Pixi的`TextStyle`函数来自定义文字效果。下面展示如何操作: ```js 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](http://pixijs.download/release/docs/PIXI.TextStyle.html)。 添加`style`对象作为`Text`函数的第二个参数来应用样式到文本上,就像这样: ```js let message = new Text("Hello Pixi!", style); ``` ![Displaying text](/examples/images/screenshots/24.5.png) 如果你想要在你创建文本对象之后改变它的内容,使用`text`属性。 ```js message.text = "Text changed!"; ``` 如果你想要重新定义样式属性,使用`style`属性。 ```js message.style = {fill: "black", font: "16px PetMe64"}; ``` Pixi通过调用Canvas绘画api将文本渲染成不可见或临时的canvas元素来创建文本对象。它之后会将画布转化为WebGL纹理,所以可以被映射到精灵上。这就是为什么文本的颜色需要被包裹成字符串:那是Canvas绘画api的颜色值。与任何canvas颜色值一样,你可以使用“red”或“green”等常用颜色的单词,或使用rgba,hsla或十六进制值。 Pixi也能包裹文本的长段。设置文本的 `wordWrap` 样式属性到 `true`,然后设置`wordWrapWidth`到一行文字应该到的最大像素。调用`align`属性来设置多行文本的对齐方式。 ```js message.style = {wordWrap: true, wordWrapWidth: 100, align: center}; ``` (注意:`align` 不会影响单行文本。) 如果你想要使用自定义的字体文件,使用CSS的`@font-face`规则来链接字体文件到Pixi应用运行的HTML页面。 ```js @font-face { font-family: "fontFamilyName"; src: url("fonts/fontFile.ttf"); } ``` 添加这个`@font-face`语句到你的HTML页面的CSS里面。 [Pixi也支持位图字体](http://pixijs.download/release/docs/PIXI.extras.BitmapText.html)。你可以使用Pixi的加载器来加载XML位图文件,就像你加载JSON或图片文件一样。 碰撞检测 -------------------------- 现在你知道了如何制造种类繁多的图形对象,但是你能用他们做什么?一个有趣的事情是利用它制作一个简单的 **碰撞检测系统** 。你可以用一个叫做:`hitTestRectangle` 的自定义的函数来检测两个矩形精灵是否接触。 ```js hitTestRectangle(spriteOne, spriteTwo) ``` 如果它们重叠, `hitTestRectangle` 会返回 `true`。你可以用 `hitTestRectangle` 结合 if 条件语句去检测两个精灵是否碰撞: ```js if (hitTestRectangle(cat, box)) { //There's a collision } else { //There's no collision } ``` 正如你所见, `hitTestRectangle` 是走入游戏设计这片宇宙的大门。 运行在 `examples` 文件夹的 `collisionDetection.html` 文件,看看怎么用 `hitTestRectangle`工作。用方向按键去移动猫,如果猫碰到了盒子,盒子会变成红色,然后 "Hit!" 文字对象会显示出来。 ![Displaying text](/examples/images/screenshots/25.png) 你已经看到了创建这些所有元素的代码,让猫移动的键盘控制。唯一的新的东西就是 `hitTestRectangle` 函数被用在 `play` 函数里检测碰撞。 ```js 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": ```js message.text = "Hit!"; ``` 这个盒子的颜色改变的效果是把盒子的 `tint` 属性改成一个16进制的红色的值实现的。 ```js box.tint = 0xff3300; ``` 如果没有碰撞,消息和盒子会保持它们的原始状态。 ```js message.text = "No collision..."; box.tint = 0xccff99; ``` 代码很简单,但是你已经创造了一个看起来完全活着的互动的世界!它简直跟魔术一样!令人惊讶的是,你大概已经拥有了你需要用Pixi制作游戏的全部技能! ### 碰撞检测函数 `hitTestRectangle` 函数都有些什么呢?它做了什么,还有它是如何工作的?关于碰撞检测算法的细节有些超出了本教程的范围。最重要的事情是你要知道如何使用它。但是,只是作为你的参考资料,不让你好奇,这里有全部的 `hitTestRectangle` 函数的定义。你能从注释弄明白它都做了什么吗? ```js 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](/examples/images/screenshots/26.png) 宝藏猎手是一个简单的完整的游戏的例子,它能让你把目前所学的所有工具都用上。用键盘的方向键可以帮助探险者找到宝藏并带它出去。六只怪物在地牢的地板上上下移动,如果它们碰到了探险者,探险者变为半透明,而且他右上角的血条会减少。如果所有的血都用完了,"You Lost!"会出现在舞台上;如果探险者带着宝藏到达了出口,显示 “You Won!” 。尽管它是一个基础的原型,宝藏猎手包含了很多大型游戏里很大一部分元素:纹理贴图集,人机交互,碰撞以及多个游戏场景。让我们一起去看看游戏是如何被它们组合起来的,以便于你可以用它作你自己开发的游戏的起点。 ### 代码结构 打开 `treasureHunter.html` 文件,你将会看到所有的代码都在一个大的文件里。下面是一个关于如何组织所有代码的概览: ```js //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`函数的缩略图和它要执行的任务。 ```js 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` 分组。他们都被添加到了舞台上。 ```js gameScene = new Container(); app.stage.addChild(gameScene); gameOverScene = new Container(); app.stage.addChild(gameOverScene); ``` 所有的的游戏主要部分的精灵都被添加到了`gameScene`分组。游戏结束的文字在游戏结束后显示,应当被添加到`gameOverScene`分组。 ![Displaying text](/examples/images/screenshots/27.png) 尽管它是在 `setup` 函数中添加的,但是 `gameOverScene`不应在游戏一开始的时候显示,所以它的 `visible` 属性被初始化为 `false`。 ```js gameOverScene.visible = false; ``` 你会在后面看到,为了在游戏结束之后显示文字,当游戏结束`gameOverScene` 的 `visible` 属性会被设置为 `true` 。 #### 制作地牢,门,猎人和宝藏 玩家、出口、宝箱和地牢背景图都是从纹理图集制作而来的精灵。有一点很重要的是,他们都是被当做 `gameScene` 的孩子添加进来的。 ```js //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` 的数组。 ```js 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` 并在舞台上被定位。 ```js //Create the health bar healthBar = new PIXI.Container(); 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` (红色的矩形)以便于过会儿能够被很方便的获取。 ```js healthBar.outer = outerBar; ``` 你可以不这么做,但是为什么不呢?这意味如果你想控制红色 `outerBar` 的宽度,你可以像这样顺畅的写如下代码: ```js healthBar.outer.width = 30; ``` 这样的代码相当整齐而且可读性强,所以我们会一直保留它! #### 制作消息文字 当游戏结束的时候, “You won!” 或者 “You lost!” 的文字会显示出来。这使用文字纹理制作的,并添加到了 `gameOverScene`。因为 `gameOverScene` 的 `visible` 属性设为了 `false` ,当游戏开始的时候,你看不到这些文字。这段代码来自 `setup` 函数,它创建了消息文字,而且被添加到了 `gameOverScene`。 ```js 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` 函数都做了什么的总体概览: ```js 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` 对象修改探险者的速度,这个速度和探险者的位置相加。 ```js explorer.x += explorer.vx; explorer.y += explorer.vy; ``` #### 控制运动的范围 一个新的地方的是,探险者的运动是被包裹在地牢的墙体之内的。绿色的轮廓表明了探险者运动的边界。 ![Displaying text](/examples/images/screenshots/28.png) 通过一个名为 `contain` 的自定义函数可以帮助实现。 ```js contain(explorer, {x: 28, y: 10, width: 488, height: 480}); ``` `contain` 接收两个参数。第一个是你想控制的精灵。第二个是包含了 `x`, `y`, `width` 和`height`属性的任何一个对象。在这个例子中,控制对象定义了一个区域,它稍微比舞台小了一点,和地牢的尺寸一样。 这里是实现了上述功能的 `contain` 函数。函数检查了精灵是否跨越了控制对象的边界。如果超出,代码会把精灵继续放在那个边界上。 `contain` 函数也返回了一个值可能为"top", "right", "bottom" 或者 "left" 的 `collision` 变量,取决于精灵碰到了哪一个边界。(如果精灵没有碰到任何边界,`collision` 将返回 `undefined` 。) ```js 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` 数组里的每一个怪物。 ```js 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` 的变量被用来捕获返回值: ```js let blobHitsWall = contain(blob, {x: 28, y: 10, width: 488, height: 480}); ``` `blobHitsWall` 通常应该是 `undefined`。但是如果怪物碰到了顶部的墙,`blobHitsWall` 将会变成 "top"。如果碰到了底部的墙,`blobHitsWall` 会变为 "bottom"。如果它们其中任何一种情况为 true,你就可以通过给怪物的速度取反来让它反向运动。这是实现它的代码: ```js if (blobHitsWall === "top" || blobHitsWall === "bottom") { blob.vy *= -1; } ``` 把怪物的 `vy` (垂直速度)乘以 `-1` 就会反转它的运动方向。 ### 检测碰撞 在上面的循环代码里用了 `hitTestRectangle` 来指明是否有敌人碰到了猎人。 ```js if(hitTestRectangle(explorer, blob)) { explorerHit = true; } ``` 如果 `hitTestRectangle` 返回 `true`,意味着发生了一次碰撞,名为 `explorerHit` 的变量被设置为了 `true`。如果 `explorerHit`为 `true`, play 函数让猎人变为半透明,然后把 `health` 条减少1像素的宽度。 ```js 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](/examples/images/screenshots/29.png) 这段代码实现了上述效果: ```js if (hitTestRectangle(explorer, treasure)) { treasure.x = explorer.x + 8; treasure.y = explorer.y + 8; } ``` ### 处理到达出口和结束游戏 游戏结束有两种方式:如果你携带宝藏到达出口你将赢得游戏,或者你的血用完你就死了。 想要获胜,宝箱只需碰到出口就行了。如果碰到了出口,游戏的 `state` 会被设置为 `end`, `message` 文字会显示 "You won!"。 ```js if (hitTestRectangle(treasure, door)) { state = end; message.text = "You won!"; } ``` 如果你的血用完,你将输掉游戏。游戏的 `state` 也会被设置为 `end`, `message` 文字会显示 "You Lost!"。 ```js if (healthBar.outer.width < 0) { state = end; message.text = "You lost!"; } ``` 但是这是什么意思呢? ```js state = end; ``` 你会在早些的例子看到 `gameLoop` 在持续的每秒60次的更新 `state` 函数。 `gameLoop` 的代码如下: ```js function gameLoop(delta){ //Update the current game state: state(delta); } ``` 你也会记住我们给 `state` 设定的初始值为 `play`,这也就是为什么 `play` 函数会循环执行。通过设置 `state` 为 `end` 我们告诉代码我们想循环执行另一个名为 `end` 的函数。在大一点的游戏你可能会为每一个游戏等级设置 `tileScene` 状态和状态集,像`leveOne`, `levelTwo` 和 `levelThree`。 `end` 函数是什么?就是它! ```js function end() { gameScene.visible = false; gameOverScene.visible = true; } ``` 它仅仅是反转了游戏场景的显示。这就是当游戏结束的时候隐藏 `gameScene` 和显示 `gameOverScene` 。 这是一个如何更换游戏状态的一个很简单的例子,但是你可以想在你的游戏里添加多少状态就添加多少状态,然后给它们添加你需要的代码。然后改变 state 为任何你想循环的函数。 这就是完成宝藏猎人所需要的一切了。然后再通过更多一点的工作就能把这个简单的原型变成一个完整的游戏 - 快去试试吧! 一些关于精灵的其他知识 ----------------------------- 目前为止你已经学会了如何用相当多有用的精灵的属性,像`x`, `y`, `visible`, 和 `rotation` ,它们让你能够让你很大程度上控制精灵的位置和外观。但是Pixi精灵也有其他很多有用的属性可以使用。 [这是一个完整的列表](http://pixijs.download/release/docs/PIXI.Sprite.html) Pixi的类继承体系是怎么工作的呢?([什么是 **类** , 什么是 **继承**?点击这个链接了解.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript))Pixi的精灵遵循以下原型链构建了一个继承模型: ``` DisplayObject > Container > Sprite ``` 继承意味着在继承链后面的类可以用之前的类的属性和方法。最基础的类是 `DisplayObject`。任何只要是`DisplayObject` 都可以被渲染在舞台上。 `Container`是继承链的下一个类。它允许 `DisplayObject`作为其他 `DisplayObject`的容器。继承链的第三个类是 `Sprite` 。这个类被你用来创建你游戏的大部分对象。然而,不久前你就会学习了如何用 `Container` 去给精灵分组。 展望未来 ----------------- Pixi能做很多事情,但是不能做全部的事情!如果你想用Pixi开始制作游戏或者复杂的交互型应用,你可能会需要一些有用的库: - [Bump](https://github.com/kittykatattack/bump): 一个为了游戏准备的完整的2D碰撞函数集. - [Tink](https://github.com/kittykatattack/tink): 拖放, 按钮, 一个通用的指针和其他有用的交互工具集。 - [Charm](https://github.com/kittykatattack/charm): 给Pixi精灵准备的简单易用的缓动动画效果。 - [Dust](https://github.com/kittykatattack/dust): 创建像爆炸,火焰和魔法等粒子效果。 - [Sprite Utilities](https://github.com/kittykatattack/spriteUtilities): 创建和使用Pixi精灵的一个更容易和更直观的做法,包括添加状态机和动画播放器。让Pixi的工作变得更有趣。 - [Sound.js](https://github.com/kittykatattack/sound.js): 一个加载,控制和生成声音和音乐效果的微型库。包含了一切你需要添加到游戏的声音。 - [Smoothie](https://github.com/kittykatattack/smoothie): 使用真正的时间增量插值实现的超平滑精灵动画。它也允许为你的运行的游戏和应用指定 fps (帧率) ,并且把你的精灵图循环渲染完全从你的应用逻辑循环中分离出去。 你可以在这儿在这本书里找到如何用结合Pixi使用这些库。 [学习PixiJS](http://www.springer.com/us/book/9781484210956). ### Hexi 如果你想使用全部的这些功能库,但又不想给自己整一堆麻烦?用 **Hexi**:创建游戏和交互应用的完整开发环境: https://github.com/kittykatattack/hexi 它把最新版本的Pixi(最新的 **稳定** 的一个)和这些库(还有更多!)打包在了一起,为了可以通过一种简单而且有趣的方式去创建游戏。Hexi 也允许你直接获取 `PIXI` 对象,所以你可直接写底层的Pixi代码,然后任意的选择你需要的Hexi额外的方便的功能。 ### BabylonJS Pixi可以很好地完成2D交互式媒体,但是对于3D却无能为力。当你准备踏进3D领域,这个最有潜力的领域的时候,不妨使用这个为WEB游戏开发者准备的用起来非常简单的3D库:[BabylonJS](https://www.babylonjs.com)。它是提升你技能的下一步。 支持这个工程 ------------------- 买这本书吧!不敢相信,有人居然会付钱让我完成这个教程并把它写成一本书! [学习 PixiJS](http://www.springer.com/us/book/9781484210956) (它可不是一本毫无价值的“电子书”,而是一本真实的,很厚的纸质书!由世界上最大的出版商,施普林格出版!这意味着你可以邀请你的朋友过来,放火,烤棉花糖!!)它比本教程多出了80%的内容,它包含了所有如何用Pixi制作所有交互应用和游戏的必要技术。) 你可以在里面学到: - 制作动画游戏角色。 - 创建一个全功能动画状态播放器。 - 动态的动画线条和形状。 - 用平铺的精灵实现无限滚动视差。 - 使用混合模式,滤镜,调色,遮罩,视频,和渲染纹理。 - 为多种分辨率生成内容。 - 创建交互按钮。 - 为Pixi创建一个灵活的拖动和拖放界面。 - 创建粒子效果。 - 建立一个可以展到任何大小的稳定的软件架构模型。 - 制作一个完整的游戏。 不仅如此,作为一个福利,所有的代码完全使用最新版本的 JavaScript:ES6/2015 编写而成的。尽管这本书基于Pixi V3.x,但是它仍旧能在Pixi 4.x上很好的运行! 如果你想支持这个项目,请买一份本书,然后再买一份给你的妈妈! 或者直接来这里慷慨的捐赠吧: http://www.msf.org