pixi.js学习总结

一.pixi.js简介

pixi.js是一个很是快速的2D精灵渲染引擎。它能够帮助咱们显示,动画和管理交互式图形。如此一来,咱们可使用javascript和其它HTML5技术来轻松实现一个应用程序或者完成一款游戏。它有一个语义化的、简洁的API,包含了许多有用的功能。好比说支持纹理地图集,也提供一个经过动画精灵(交互式图像)所构建的精简的系统。它还为咱们提供了一个完整的场景图,咱们能够建立嵌套精灵的层次结构(也就是精灵当中嵌套精灵)。一样的也容许咱们将鼠标以及触摸的事件添加到精灵上。并且,最重要的仍是,pixi.js能够根据咱们的实际需求来使用,很好的适应我的的编码风格,而且还可以和框架无缝集成。javascript

pixi.js的API事实上是陈旧的Macromedia/Adobe Flash API的一个改进。熟练flash的开发人员将会有一种回到本身家里同样的熟悉。其它使用过相似API的精灵渲染框架有: CreateJS,Starling, Sparrow 和 Apple’s SpriteKit。pixi.js的优势在于它的通用性:它不是一个游戏引擎。这是极好的,由于它能够彻底任由咱们自由发挥,作本身的事情,甚至还能够用它写一个本身的游戏引擎。在学习pixi.js以前,咱们须要先对HTML,CSS,Javascript有一些了解。由于这会对咱们学习pixi.js有很大的帮助。css

二.pixi.js安装

1.前往github安装

能够前往github上安装,在这里。也能够在这里下载安装。html

2.使用npm安装

也可使用npm来安装。首先须要安装node.js。 当安装node.js完成以后,会自动完成npm的安装。而后就能够经过npm将pixi.js安装在全局。命令以下:html5

npm install pixi.js -g
//也能够简写为
npm i pixi.js -g
复制代码

或者也能够将pixi.js安装在当前项目的目录之下。命令以下:java

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文件。代码以下:node

<script src="/pixi.min.js"></script>
复制代码

或者,你也可使用CDN来引入这个js文件。以下:git

<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没有默认导出。因此正确的引入方式应该以下:es6

import * as PIXI from 'pixi.js'

复制代码

2.一个简单的示例

好了,接下来就是写点js代码,看看pixi.js是否在工做中。代码以下:github

const type = "WebGL";
if (!PIXI.utils.isWebGLSupported()) {
    type = "canvas";
}
PIXI.utils.sayHello(type);

复制代码

若是能在浏览器控制台中,看到以下图所示的标志,那么就证实pixi.js加载成功。web

1

咱们来分析一下以上的代码的意思,先定义一个变量,值是字符串"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。相似的工具(例如Shoeboxspritesheet.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:本文基于官方教程而翻译并加入了我的的理解以及示例,不喜勿喷。本文总结在本人的我的网站上)。

相关文章
相关标签/搜索