PIXIJS简易教程
发现一款超好用的2D渲染引擎—PixiJS,非常适合制作H5小游戏。
pixiJS学习网址如下:
https://aitrade.ga/pixi.js-cn/PIXI.Sprite.html
https://www.bookstack.cn/read/LearningPixi/introduction
学习一门技术最好的方法就是做个demo,所以写了个简易版的飞机大战。效果如下:
视频不太好放就弄张截图。豌豆侠用豌豆去击落小鸟,小鸟碰到豌豆就game Over。用PixiJS开发H5小小游戏速度是很快,而且画面流畅。大致实现如下:
创建应用和画布
let app = new PIXI.Application({width: 256, height: 256});
document.body.appendChild(app.view);
这段代码相当于创建了一个黑色背景的canvas标签,其他设置可以参考官网。
加载图片资源
画布建好后,需要往里面添加图像,我们需要创建精灵来使这些图像可以被代码控制。精灵是如何被创建和显示的呢?
先加载图片资源,我们需要的图片资源有:一张背景图、豌豆侠、充当子弹的豌豆、愤怒鸟。图片加载完成后,在load里进行后续的操作。
let Application = PIXI.Application;
loader.add(
[
{name: "cover", url: "images/cover.jpg"},
{name: "snake", url: "images/my.png"},
{name: "bullet", url: "images/pea.png"},
{name: "enemy", url: "images/bird.gif"}
]
).load(setup)
添加精灵
精灵是能用代码控制图像的基础,我们需要创建游戏需要的精灵,以豌豆侠为例:
从纹理缓存中取出豌豆侠图片,为该图片创建一个精灵,并设置该图片的位置大小等信息,再将该精灵添加到舞台中。
let my = TextureCache["my"];
let mySprite = new Sprite(my);
mySprite.width = 50;
mySprite.height = 50;
mySprite.x = 0;
mySprite.y = ch - mySprite.height;
mySprite.vx = 0;
mySprite.vy = 0;
app.stage.addChild(mySprite); // 将精灵添加到舞台
按键移动豌豆侠
Pixi的ticker可以实现精灵移动,ticker中的代码一秒可以执行60次,将需要改变的位置信息放入里面。左键(左移)右键(右移)上键(发射),以右移为例:
right.press = () => {
mySprite.vx = 5; // 速度
mySprite.vy = 0;
};
right.release = () => {
if (right.isDown) {
mySprite.vx = 0;
}
};
app.ticker.add(delta => gameLoop(delta));
function gameLoop(delta) {
mySprite.x += mySprite.vx;
}
发射豌豆
豌豆的创建和发射也是类似道理:为豌豆创建一个精灵,设置该精灵的大小和其他信息,位置设置为豌豆侠的位置。由于玩家可以持续按发射键,画布上会出现多粒豌豆的情况,所以创建一个数组放置多颗豌豆,记录点击次数,当次数超过豌豆数组长度时,点击次数重新置为0。
// 发射子弹
up.press = () => {
bullets[bulletIndex].visible = up;
bullets[bulletIndex].x = mySprite.x;
bullets[bulletIndex].y = mySprite.y;
bulletIndex++;
if (bulletIndex == bulletNum - 1) {
bulletIndex = 0;
}
};
创建愤怒鸟
循环创建愤怒鸟,并随机设置其位置信息。
let enemy = TextureCache["enemy"];
for (let i = 0; i < enemyNum; i++) {
let enemySprite = new Sprite(enemy);
enemySprite.width = 40;
enemySprite.height = 40;
enemySprite.y = getRandom(-1000, 0);
enemySprite.x = getRandom(0, cw); // 随机出现位置
enemySprite.vx = 0;
enemySprite.vy = -2;
app.stage.addChild(enemySprite);
enemys.push(enemySprite);
}
碰撞检测
几处地方需要检测碰撞:愤怒鸟碰到豌豆侠游戏结束、豌豆碰到愤怒鸟双双消失、豌豆侠只能左右移动且无法穿墙。注意图片的消失只是图片暂时被隐藏了,当它运动出画框还是会被重新显示。
一个比较粗糙的H5小游戏就完成了。代码也很粗糙,先贴出来,后续再完善:
window.onload = function () {
initGame();
}
// 初始化游戏
function initGame() {
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 cw = document.documentElement.clientWidth;
let ch = document.documentElement.clientHeight;
let enemys = [];
let bulletNum = 100; // 页面最多可以显示100个子弹
let enemyNum = 10; // 页面最多可以显示100个敌机
let app = new Application({
width: cw,
height: ch,
antialias: true,
transparent: false,
resolution: 1
}
);
document.body.appendChild(app.view);
loader.add(
[
{name: "cover", url: "images/cover.jpg"},
{name: "my", url: "images/my.png"},
{name: "bullet", url: "images/pea.png"},
{name: "enemy", url: "images/bird.gif"}
]
).load(setup)
function setup() {
let cover = TextureCache["cover"];
let coverSprite = new Sprite(cover);
app.stage.addChild(coverSprite);
let state;
let bullets = [];
let bulletIndex = 0; // 第几个子弹
// 我方
let my = TextureCache["my"];
let mySprite = new Sprite(my);
mySprite.width = 50;
mySprite.height = 50;
mySprite.x = 0;
mySprite.y = ch - mySprite.height;
mySprite.vx = 0;
mySprite.vy = 0;
app.stage.addChild(mySprite);
initBullet(bullets);
initEnemy();
let left = keyboard(37),
up = keyboard(38),
right = keyboard(39),
down = keyboard(40);
// 我方移动(左右)
left.press = () => {
mySprite.vx = -5;
mySprite.vy = 0;
};
left.release = () => {
if (left.isDown) {
mySprite.vx = 0;
}
};
right.press = () => {
mySprite.vx = 5; // 速度
mySprite.vy = 0;
};
right.release = () => {
if (right.isDown) {
mySprite.vx = 0;
}
};
// 发射子弹
up.press = () => {
bullets[bulletIndex].visible = up;
bullets[bulletIndex].x = mySprite.x;
bullets[bulletIndex].y = mySprite.y;
bulletIndex++;
if (bulletIndex == bulletNum - 1) {
bulletIndex = 0;
}
};
state = play;
app.ticker.add(delta => gameLoop(delta));
function gameLoop(delta) {
state(delta);
}
// 页面1秒刷新60次
function play(delta) {
mySprite.x += mySprite.vx;
// 墙壁触碰
if (mySprite.x <= 0) {
mySprite.x = 0
}
if (mySprite.x >= cw - mySprite.width) {
mySprite.x = cw - mySprite.width
}
bullets.forEach(m => {
m.y += m.vy;
})
enemys.forEach(m => {
m.y -= m.vy;
// 出框则位置改变
if (m.y > ch) {
m.y = getRandom(-1000, 0);
m.visible = true;
}
// 我方触到敌机
if (isCollision(mySprite, m) && m.visible) {
alert('Game Over!');
}
})
bullets.forEach(m => {
for (let i = 0; i < enemys.length; i++) {
if (isCollision(m, enemys[i]) && m.visible && enemys[i].visible) {
m.visible = false;
enemys[i].visible = false;
}
}
})
}
}
// 初始化子弹
function initBullet(bullets) {
let bullet = TextureCache["bullet"];
for (let i = 0; i < bulletNum; i++) {
let bulletSprite = new Sprite(bullet);
bulletSprite.width = 30;
bulletSprite.height = 30;
bulletSprite.vx = 0;
bulletSprite.vy = -5;
bulletSprite.visible = false;
app.stage.addChild(bulletSprite);
bullets.push(bulletSprite);
}
}
// 初始化敌机
function initEnemy() {
// 敌机
let enemy = TextureCache["enemy"];
for (let i = 0; i < enemyNum; i++) {
let enemySprite = new Sprite(enemy);
enemySprite.width = 40;
enemySprite.height = 40;
enemySprite.y = getRandom(-1000, 0);
enemySprite.x = getRandom(0, cw); // 随机出现位置
enemySprite.vx = 0;
enemySprite.vy = -2;
app.stage.addChild(enemySprite);
enemys.push(enemySprite);
}
}
// 碰撞检测
function isCollision(r1, r2) {
let hit, combinedHalfWidths, combinedHalfHeights, vx, vy;
hit = false;
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;
r1.halfWidth = r1.width / 2;
r1.halfHeight = r1.height / 2;
r2.halfWidth = r2.width / 2;
r2.halfHeight = r2.height / 2;
vx = r1.centerX - r2.centerX;
vy = r1.centerY - r2.centerY;
combinedHalfWidths = r1.halfWidth + r2.halfWidth;
combinedHalfHeights = r1.halfHeight + r2.halfHeight;
if (Math.abs(vx) < combinedHalfWidths) {
if (Math.abs(vy) < combinedHalfHeights) {
hit = true;
} else {
hit = false;
}
} else {
hit = false;
}
return hit;
}
}
function keyboard(keyCode) {
let key = {};
key.code = keyCode;
key.isDown = false;
key.isUp = true;
key.press = undefined;
key.release = undefined;
key.downHandler = event => {
if (event.keyCode === key.code) {
if (key.isUp && key.press) key.press();
key.isDown = true;
key.isUp = false;
}
event.preventDefault();
};
key.upHandler = event => {
if (event.keyCode === key.code) {
if (key.isDown && key.release) key.release();
key.isDown = false;
key.isUp = true;
}
event.preventDefault();
};
window.addEventListener(
"keydown", key.downHandler.bind(key), false
);
window.addEventListener(
"keyup", key.upHandler.bind(key), false
);
return key;
}
// 随机数生成
function getRandom(min, max) {
return Math.round(Math.random() * (max - min)) + min
}