前面两篇已将讲述如何显示精灵,但是如何使它们移动?这很简单:
- 使用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
- 1000代表
// 令explorer精灵持续右移
// 加入到ticker的函数,每秒被调用60次
this.app.ticker.add((delta) => this.gameLoop(explorer));
-
可以使用两个 速度属性(
vx
和vy
)来控制精灵的移动速度,无需直接更改精灵的x
和y
值vx
: 用于设置精灵在x
轴上的速度和方向vy
: 用于在y
轴上设置精灵的速度和方向
-
首先在精灵上创建
vx
和vy
属性,并给一个初始值,0代表初始不移动
explorer.vx = 0;
explorer.vy = 0;
- 在游戏循环中,更新精灵的移动速度
vx
和vy
gameLoop(sprite) {
sprite.vx = 1;
sprite.vy = 1;
sprite.x += sprite.vx;
sprite.y += sprite.vy;
},
键盘移动
- 只需要监听并捕获键盘事件,就可以做到使用键盘控制精灵
- 自定义一个监听捕获键盘事件
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);
容器(Container)
可视为一种不包含纹理的特殊精灵,将整个精灵分组视为一个单元- 用
children
属性列出包含的所有精灵
console.log(spriteGroup.children); // [Sprite, Sprite, Sprite]
- 精灵分组和单个精灵类似,同样拥有精灵的属性(如
x
、y
、alpha
、scale
),在整体上更改属性值都将以一种相对的方式影响子精灵
// 使精灵分组整体将向右移动100个像素,向下移动100个像素
spriteGroup.position.set(100, 100);
// 获取精灵尺寸
console.log(spriteGroup.width);
console.log(spriteGroup.height);
- 注意: 一个可显示对象(单个精灵或精灵分组)只能拥有一个父级,如果将其中一个精灵添加到另一个容器,
Pixi
将自动从当前父容器中移除它
精灵坐标
- 将精灵添加到容器中时,
x
和y
坐标相对于分组的左上角,x
和y
就是局部坐标 - 如获取精灵
explorer
的x
坐标
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
存在,先将from
与position
参数一起求出from
的全局坐标
newPosition = from.toGlobal(position, point, skipupdate)
- 再求出显示对象位置与这个全局坐标
newPosition
的偏移坐标
spritr.toLocal(newPosition)
- 如果
from
为空,就是计算显示对象与position
的相对偏移量
- 若
使用粒子容器
-
Pixi
有另一种高性能的方式来组合精灵,称为粒子容器ParticleContainer
-
任何在粒子容器中的精灵渲染速度比在常规容器中快2到5倍
-
创建一个粒子容器
ParticleContainer
,需要注意:- 粒子容器的精灵只有基本属性:
x
、y
、width
、height
、scale
、pivot
、alpha
、visible
等 - 粒子容器不能使用
Pixi
的高级视觉效果,如过滤器和混合模式 - 每个粒子容器只能使用一个纹理
- 创建粒子容器时,有四个可选参数:
maxSize
、properties
、batchSize
和autoResize
- 粒子容器的精灵只有基本属性:
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);