Pixi.js的基本使用(3)--移动精灵与组合精灵

895 阅读6分钟

前面两篇已将讲述如何显示精灵,但是如何使它们移动?这很简单:

  • 使用Pixi的 ticker 创建一个循环函数,这被称为游戏循环(game loop)
  • 放入游戏循环的任何代码,每秒都会被执行60次
  • 例如下面的代码,只要执行都会使精灵以每帧1像素的速度向右移动
 // 放入的精灵会以每帧1像素的速度向右移动
 gameLoop(sprite) {
   sprite.x += 1; // 向左移动则设置为-1
 },
  • ticker 函数提供了一个参数 delta,表示帧之间的延迟分量

  • delta = (当前帧的时间 - 上一帧的时间) / (1000 / 60)

    • 1000代表 1000ms, 60是1秒钟执行60次
    • 如果设备能做到60帧/秒,帧与帧之间的间隔相差不大,就不考虑 delta
 // 令explorer精灵持续右移
 // 加入到ticker的函数,每秒被调用60次
 this.app.ticker.add((delta) => this.gameLoop(explorer));
  • 可以使用两个 速度属性vxvy )来控制精灵的移动速度无需直接更改精灵的 xy

    • vx 用于设置精灵在 x 轴上的速度和方向
    • vy 用于在 y 轴上设置精灵的速度和方向
  • 首先在精灵上创建 vxvy 属性,并给一个初始值,0代表初始不移动

 explorer.vx = 0;
 explorer.vy = 0;
  • 在游戏循环中,更新精灵的移动速度 vxvy
 gameLoop(sprite) {
   sprite.vx = 1;
   sprite.vy = 1;
   sprite.x += sprite.vx;
   sprite.y += sprite.vy;
 },

image-20230210173053616.png

键盘移动

  • 只需要监听并捕获键盘事件就可以做到使用键盘控制精灵
  • 自定义一个监听捕获键盘事件 keyboard 函数
 const keyboard = (value) => {
   const key = {
     value,
     isDown: false,
     isUp: true,
     press: undefined, // 按键按下事件
     release: undefined, // 按键释放事件
   };
 ​
   // 按下
   key.handleDown = (e) => {
     if (e.key !== key.value) return;
     if (key.isUp && key.press) key.press();
     // 改变按键的状态
     key.isDown = true;
     key.isUp = false;
     e.preventDefault();
   };
 ​
   // 抬起
   key.handleUp = (e) => {
     if (e.key !== key.value) return;
     if (key.isDown && key.release) key.release();
     // 改变按键的状态
     key.isDown = false;
     key.isUp = true;
     e.preventDefault();
   };
 ​
   // 添加事件监听
   const downListener = key.handleDown.bind(key);
   const upListener = key.handleUp.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 left = keyboard("ArrowLeft"),
     right = keyboard("ArrowRight"),
     up = keyboard("ArrowUp"),
     down = keyboard("ArrowDown");
  • 对上下左右方向键定义按下和释放事件
 // 左
 left.press = () => {
   sprite.vx = -2;
   sprite.vy = 0;
 };
 left.release = () => {
   if (!right.isDown && sprite.vy === 0) sprite.vx = 0;
 };
 ​
 // 右
 right.press = () => {
   sprite.vx = 2;
   sprite.vy = 0;
 };
 right.release = () => {
   if (!left.isDown && sprite.vy === 0) sprite.vx = 0;
 };
 ​
 // 上
 up.press = () => {
   sprite.vy = -2;
   sprite.vx = 0;
 };
 up.release = () => {
   if (!down.isDown && sprite.vx === 0) sprite.vy = 0;
 };
 ​
 // 下
 down.press = () => {
   sprite.vy = 2;
   sprite.vx = 0;
 };
 down.release = () => {
   if (!up.isDown && sprite.vx === 0) sprite.vy = 0;
 };
  • 按键后更改精灵速度,触发游戏循环
 this.playGame(sprite);
 ​
 // 游戏函数
 playGame(sprite) {
   sprite.x += sprite.vx;
   sprite.y += sprite.vy;
 },
  • 至此就可以控制精灵上下左右移动

精灵分组

  • 在创建游戏场景时可以给精灵分组,作为一个整体管理
  • 加载多个精灵时,使用数组的方式加载
 // 创建纹理
 loadResource(sourceUrl) {
   const textureMap = []; // 初始化一个纹理数组
   const loader = PIXI.Loader.shared;
   loader.reset(); // 有缓存,需要重置loader
   PIXI.utils.clearTextureCache(); // 清除纹理缓存
   loader.add(sourceUrl); // 加载图片资源
   return new Promise((resolve) => {
     loader.load((loader, resource) => {
       // 使用循环将每个精灵的纹理保存
       Object.keys(resource).forEach((item, i) => {
         textureMap[i] = resource[item].texture;
       });
       resolve(textureMap);
     });
   });
 },
   
 const textureMap = await this.loadResource([
   require("@/assets/images/treasure.png"),
   require("@/assets/images/blob.png"),
   require("@/assets/images/explorer.png"),
 ]); // 加载资源
  • 加载完成后,使用 Container 对象进行组合
 // 创建并初始化explore精灵位置
 const explorer = new PIXI.Sprite(texture[0]);
 explorer.position.set(32, 32);
 ​
 // 创建并初始化blob精灵位置
 const blob = new PIXI.Sprite(texture[1]);
 blob.position.set(64, 64);
 ​
 // 创建并初始化treasure精灵位置
 const treasure = new PIXI.Sprite(texture[2]);
 treasure.position.set(96, 96);
 ​
 // 创建spriteGroup容器
 const spriteGroup = new PIXI.Container();
 // 将精灵添加到容器分组内
 spriteGroup.addChild(explorer);
 spriteGroup.addChild(blob);
 spriteGroup.addChild(treasure);
 // 将分组添加到舞台
 this.app.stage.addChild(spriteGroup);

image-20230213160335102.png

  • 容器(Container) 可视为一种不包含纹理的特殊精灵,将整个精灵分组视为一个单元
  • children 属性列出包含的所有精灵
 console.log(spriteGroup.children); // [Sprite, Sprite, Sprite]
  • 精灵分组和单个精灵类似,同样拥有精灵的属性(如 xyalphascale),在整体上更改属性值都将以一种相对的方式影响子精灵
 // 使精灵分组整体将向右移动100个像素,向下移动100个像素
 spriteGroup.position.set(100, 100);
 ​
 // 获取精灵尺寸
 console.log(spriteGroup.width);
 console.log(spriteGroup.height);
  • 注意: 一个可显示对象(单个精灵或精灵分组)只能拥有一个父级,如果将其中一个精灵添加到另一个容器,Pixi 将自动从当前父容器中移除它

精灵坐标

  • 将精灵添加到容器中时,xy 坐标相对于分组的左上角,xy就是局部坐标
  • 如获取精灵 explorerx 坐标
 explorer.x; // 32
  • 精灵也有一个全局坐标,全局坐标是从舞台左上角到精灵的定位点(通常是精灵的左上角)的距离
  • 使用 toGlobal 方法查看精灵的全局坐标
 // 找到explorer精灵的全局坐标
 spriteGroup.toGlobal(explorer.position) // Point {x: 132, y: 132}
  • 总的来说,局部坐标是精灵相对父容器左上角的位置全局坐标是精灵相对舞台左上角的位置
  • 如果不知道精灵的父容器是谁,可以通过精灵的 parent 属性找到其父容器
 // 获取explorer精灵的父容器
 explorer.parent;
 ​
 // 通过父容器获取精灵的全局坐标
 explorer.parent.toGlobal(explorer.position);
  • 可以通过精灵的 getGlobalPosition 方法获取到精灵的全局坐标

    • getGlobalPosition 是高度精确的,它可以实时监听精灵的全局坐标变化,保证精灵全局坐标的准确
    • getGlobalPosition 专门为游戏的碰撞检测而生
 explorer.getGlobalPosition().x; // 132
 explorer.getGlobalPosition().y; // 132
  • 通过 toLocal 方法,可以查看精灵与精灵之间的距离

    • 计算显示对象相对于另一个点的局部位置,返回一个 Point 对象
 // treasure精灵与explorer精灵的相对位置
 explorer.toLocal(treasure.position) // Point {x: 168, y: 168}
 // 表示treasure精灵的左上角 位于 刺猬左上角向右向下偏移168像素
  • toLocal(position, from, point, skipupdate) 参数说明

    • position 一个自定义坐标,计算时该坐标作为临时坐标的原点
    • from 另一个显示对象
    • point Point 对象,等同于返回值,可省略
    • skipupdate 默认为 false
  • 计算过程:

    • from 存在,先将 fromposition 参数一起求出 from 的全局坐标
     newPosition = from.toGlobal(position, point, skipupdate)
    
    • 再求出显示对象位置与这个全局坐标 newPosition 的偏移坐标
     spritr.toLocal(newPosition)
    
    • 如果 from 为空,就是计算显示对象与 position 的相对偏移量

使用粒子容器

  • Pixi 有另一种高性能的方式来组合精灵,称为粒子容器 ParticleContainer

  • 任何在粒子容器中的精灵渲染速度比在常规容器中快2到5倍

  • 创建一个粒子容器 ParticleContainer,需要注意:

    • 粒子容器的精灵只有基本属性xywidthheightscalepivotalphavisible
    • 粒子容器不能使用 Pixi 的高级视觉效果,如过滤器和混合模式
    • 每个粒子容器只能使用一个纹理
    • 创建粒子容器时,有四个可选参数:maxSizepropertiesbatchSizeautoResize
 const fastSprite = new PIXI.ParticleContainer(maxSize, properties, batchSize, autoResize)
  • 参数解析:

    • maxSize 容器可以渲染的最大粒子数
    • properties 子画面所应用的属性对象(5个布尔值) {vertices,position,rotation,uvs,tint}设置为false可以最大限度地发挥性能
    • batchSize 每批次的粒子数
    • autoResize 如果为 true,容器分配更多批次
  • 使用 addChild 向容器添加精灵,像使用普通容器一样

 for (let i = 0; i < 100; ++i) {
   let sprite = new PIXI.Sprite(texture);
   sprite.position.set(4 * i, 5 * i);
   fastSprite.addChild(sprite);
 }
 this.app.stage.addChild(fastSprite);

image-20230214112806309.png