作者:广树时间:2018-09-12 13:47:31分类:JavaScript
前情回顾
上回链接:《2D WebGL renderer Pixi.js v4 入门【第五回】》
上回学习了WebGL的各种图像与文字的绘制。
检测碰撞
上图为理想状态。
左边的猫能够使用键盘操作,右边的块是无法移动的,然后上面的信息提示状态。
在基于<第三回>的键盘控制所写的代码中,
改造和追加了一部分代码。
var 猫, 块, 消息, 状态;
function setup(){
放入猫;
制作块;
消息初始化;
设置键盘事件的移动数值;
将状态设定为play;
gameLoop();
}
function play(){
接收键盘的移动数值;
if(碰撞){
变化块的颜色和消息;
}else{
回滚块的颜色和消息;
}
}
function 碰撞(r1, r2){
查找每个元素的中心点;
计算每个元素一半的高和一半的宽;
r1的一半高度和r2的一半高度相加;
r1的一半宽度和r2的一半宽度相加;
if(r1,r2的中心点距离 < r1,r2 相加的距离){
碰撞;
}
}
翻译成代码就是这样
var cat, box, message, state;
function setup() {
box = new PIXI.Graphics();
box.beginFill(0xCCFF99);
box.drawRect(0, 0, 64, 64);
box.endFill();
box.x = 120;
box.y = 96;
stage.addChild(box);
cat = new Sprite(resources["images/cat.png"].texture);
cat.x = 16;
cat.y = 96;
cat.vx = 0;
cat.vy = 0;
stage.addChild(cat);
var left = keyboard(37),
up = keyboard(38),
right = keyboard(39),
down = keyboard(40);
left.press = function() {
cat.vx = -5;
cat.vy = 0;
};
left.release = function() {
if (!right.isDown && cat.vy === 0) {
cat.vx = 0;
}
};
up.press = function() {
cat.vy = -5;
cat.vx = 0;
};
up.release = function() {
if (!down.isDown && cat.vx === 0) {
cat.vy = 0;
}
};
right.press = function() {
cat.vx = 5;
cat.vy = 0;
};
right.release = function() {
if (!left.isDown && cat.vy === 0) {
cat.vx = 0;
}
};
down.press = function() {
cat.vy = 5;
cat.vx = 0;
};
down.release = function() {
if (!up.isDown && cat.vx === 0) {
cat.vy = 0;
}
};
message = new PIXI.Text(
"No collision...",
{font: "18px sans-serif", fill: "white"}
);
message.position.set(8, 8);
stage.addChild(message);
state = play;
gameLoop();
}
function play() {
cat.x += cat.vx;
cat.y += cat.vy;
if (hitTestRectangle(cat, box)) {
message.text = "hit!";
box.tint = 0xff3300;
} else {
message.text = "No collision...";
box.tint = 0xccff99;
}
}
function hitTestRectangle(r1, r2) {
var 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;
};
分析之后,发现还挺简单的。
如果没问题的话就进入正题吧。
寻宝游戏
游戏原型大致是这样:避开怪物,获取宝物,从门那里出去后成功。如果碰到怪物的话减少HP。减完了则判断为输。
代码设计
// pixi.js的初始化以及图像的读取
function setup() {
// 初始化,然后游戏状态切换为play。
// gameLoop();
}
function gameLoop() {
// 描绘,并将精灵传递到render。
}
function play() {
// 游戏所有的逻辑写这里。
}
function end() {
// 游戏结束后。
}
// 其他必要的helper functions:
// `keyboard`, `hitTestRectangle`, `contain` and `randomInt`
首先将游戏场景和游戏结束场景分开。(不同的Container)
gameScene = new Container(); stage.addChild(gameScene); gameOverScene = new Container(); stage.addChild(gameOverScene);
然后,先将gameOverScene隐藏。
gameOverScene.visible = false;
游戏场景
首先将简单的洞窟背景、门、勇者、宝箱描绘出来。
id = resources["images/treasureHunter.json"].textures; //洞窟 dungeon = new Sprite(id["dungeon.png"]); gameScene.addChild(dungeon); //门 door = new Sprite(id["door.png"]); door.position.set(32, 0); gameScene.addChild(door); //勇者 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 = new Sprite(id["treasure.png"]); treasure.x = gameScene.width - treasure.width - 48; treasure.y = gameScene.height / 2 - treasure.height / 2; gameScene.addChild(treasure);
接下来制作随机移动的怪物。
// 设定怪物数量
var numberOfBlobs = 6,
// 怪物与怪物之间的间距
spacing = 48,
// 从左边开始最初的空隙
xOffset = 150,
// 速度
speed = 2,
// 方向
direction = 1;
// 储存怪物的数组
blobs = [];
// 按照设定的数值描绘怪物
for (var i = 0; i < numberOfBlobs; i++) {
// 怪物出生(?)
var blob = new Sprite(id["blob.png"]);
// 空出沿x轴一定距离的空间
var x = spacing * i + xOffset;
// y坐标(随机)
var y = randomInt(0, stage.height - blob.height);
// 给与坐标
blob.x = x;
blob.y = y;
// 方向(direction)为1的话向下,-1的话向上
// 方向 乘以 速度
blob.vy = speed * direction;
// 下一只怪物为反方向
direction *= -1;
// 配置到数组中
blobs.push(blob);
gameScene.addChild(blob);
}
然后是血条
healthBar = new PIXI.DisplayObjectContainer(); healthBar.position.set(stage.width - 170, 6) gameScene.addChild(healthBar); // HP血条的空间 var innerBar = new PIXI.Graphics(); innerBar.beginFill(0x000000); innerBar.drawRect(0, 0, 128, 8); innerBar.endFill(); healthBar.addChild(innerBar); // HP血条的HP值 var outerBar = new PIXI.Graphics(); outerBar.beginFill(0xFF3300); outerBar.drawRect(0, 0, 128, 8); outerBar.endFill(); healthBar.addChild(outerBar); healthBar.outer = outerBar;
这里出现的DisplayObjectContainer和Container和ParticleContainer的差异非常明显呢。
另外,outer的意义现在还不是很清楚,等知道了补充。
游戏结束场景
准备好游戏结束后的生理和失败的消息。
// 写在之前隐藏gameOverScene代码的后面
message = new Text(
"The End!",
{font: "64px Futura", fill: "white"}
);
message.x = 120;
message.y = stage.height / 2 - 32;
gameOverScene.addChild(message);
play function
已经准备好登场人物和怪物了,现在将游戏时要做什么列了一张表。
勇者移动,移动范围限制。
怪物的移动,移动范围限制,碰撞到墙壁时的反馈。
勇者和怪物碰撞了吗?
勇者和宝箱碰撞了吗?(获得宝物)
勇者和门碰撞了吗?(成功逃出)
游戏成功与失败的判定
然后,整理一下吧!
作为helper function集中起来由play function调用比较好的有
范围限制(判断是否在边界)
碰撞判断
那么,改变代码吧
// 范围限制(判断是否在边界)
// 这里放入的值是能够移动、移动中的精灵(这里为勇者和怪物)和可移动范围
function contain(sprite, container) {
var 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";
}
// 返回值
return collision;
}
// 判断是否碰撞的函数
function hitTestRectangle(r1, r2) {
var 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 randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
放入play function
function play() {
// 用键盘事件中获取的数值来移动
explorer.x += explorer.vx;
explorer.y += explorer.vy;
// 勇者的移动范围
contain(explorer, {x: 28, y: 10, width: 488, height: 480});
//contain(explorer, stage);
// 给勇者一个没有碰撞的初始设定
var explorerHit = false;
// 使用怪物的分布
blobs.forEach(function(blob) {
// 使其移动
blob.y += blob.vy;
// 给怪物一个移动范围的限制,捕捉返回的值
var blobHitsWall = contain(blob, {x: 28, y: 10, width: 488, height: 480});
// 判断返回的值,将方向反向
if (blobHitsWall === "top" || blobHitsWall === "bottom") {
blob.vy *= -1;
}
// 如果和勇者碰上了,就改变刚才碰撞的值
if(hitTestRectangle(explorer, blob)) {
explorerHit = true;
}
});
// 如果勇者和怪物碰上了
if(explorerHit) {
// 一瞬间半透明
explorer.alpha = 0.5;
// HP削减
healthBar.outer.width -= 1;
} else {
// 从半透明恢复
explorer.alpha = 1;
}
// 如果勇者和宝箱碰撞
if (hitTestRectangle(explorer, treasure)) {
// 做一个手持宝箱的样子(宝箱的位置一直跟随在勇者右下角)
treasure.x = explorer.x + 8;
treasure.y = explorer.y + 8;
}
// HP归零则游戏结束,弹出失败信息
if (healthBar.outer.width < 0) {
state = end;
message.text = "You lost!";
}
// 宝箱(和勇者)和门碰撞,游戏结束,弹出成功信息
if (hitTestRectangle(treasure, door)) {
state = end;
message.text = "You won!";
}
}
然后用end function切换场景
function end() {
gameScene.visible = false;
gameOverScene.visible = true;
}
游戏完成
补充:关于精灵
精灵除了坐标、visible、旋转之外还有各种设置,详情可以在官方文档中查找。
连接如下
基本上精灵是遵循继承规则的
DisplayObject > Container > Sprite
就此教程结束。
各位看官辛苦了。
本回的代码:进入git
本文翻译自:2D WebGL renderer Pixi.js v4【連載第六回】、2D WebGL renderer Pixi.js v4【連載最終回】
翻译者:广树
转载请注明出处!











