上回學(xué)習了WebGL的各種圖像與文字的繪制。
檢測碰撞
上圖為理想狀態(tài)。
左邊的貓能夠使用鍵盤(pán)操作,右邊的塊是無(wú)法移動(dòng)的,然后上面的信息提示狀態(tài)。
在基于<第三回>的鍵盤(pán)控制所寫(xiě)的代碼中,
改造和追加了一部分代碼。
var 貓, 塊, 消息, 狀態(tài);
function setup(){
放入貓;
制作塊;
消息初始化;
設置鍵盤(pán)事件的移動(dòng)數值;
將狀態(tài)設定為play;
gameLoop();
}
function play(){
接收鍵盤(pán)的移動(dòng)數值;
if(碰撞){
變化塊的顏色和消息;
}else{
回滾塊的顏色和消息;
}
}
function 碰撞(r1, r2){
查找每個(gè)元素的中心點(diǎn);
計算每個(gè)元素一半的高和一半的寬;
r1的一半高度和r2的一半高度相加;
r1的一半寬度和r2的一半寬度相加;
if(r1,r2的中心點(diǎn)距離 < 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;
};
分析之后,發(fā)現還挺簡(jiǎn)單的。
如果沒(méi)問(wèn)題的話(huà)就進(jìn)入正題吧。
尋寶游戲
游戲原型大致是這樣:避開(kāi)怪物,獲取寶物,從門(mén)那里出去后成功。如果碰到怪物的話(huà)減少HP。減完了則判斷為輸。
代碼設計
// pixi.js的初始化以及圖像的讀取
function setup() {
// 初始化,然后游戲狀態(tài)切換為play。
// gameLoop();
}
function gameLoop() {
// 描繪,并將精靈傳遞到render。
}
function play() {
// 游戲所有的邏輯寫(xiě)這里。
}
function end() {
// 游戲結束后。
}
// 其他必要的helper functions:
// `keyboard`, `hitTestRectangle`, `contain` and `randomInt`
首先將游戲場(chǎng)景和游戲結束場(chǎng)景分開(kāi)。(不同的Container)
gameScene = new Container();
stage.addChild(gameScene);
gameOverScene = new Container();
stage.addChild(gameOverScene);
然后,先將gameOverScene隱藏。
gameOverScene.visible = false;
游戲場(chǎng)景
首先將簡(jiǎn)單的洞窟背景、門(mén)、勇者、寶箱描繪出來(lái)。
id = resources["images/treasureHunter.json"].textures;
//洞窟
dungeon = new Sprite(id["dungeon.png"]);
gameScene.addChild(dungeon);
//門(mén)
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);
接下來(lái)制作隨機移動(dòng)的怪物。
// 設定怪物數量
var numberOfBlobs = 6,
// 怪物與怪物之間的間距
spacing = 48,
// 從左邊開(kāi)始最初的空隙
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的話(huà)向下,-1的話(huà)向上
// 方向 乘以 速度
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的意義現在還不是很清楚,等知道了補充。
游戲結束場(chǎng)景
準備好游戲結束后的生理和失敗的消息。
// 寫(xiě)在之前隱藏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
已經(jīng)準備好登場(chǎng)人物和怪物了,現在將游戲時(shí)要做什么列了一張表。
勇者移動(dòng),移動(dòng)范圍限制。
怪物的移動(dòng),移動(dòng)范圍限制,碰撞到墻壁時(shí)的反饋。
勇者和怪物碰撞了嗎?
勇者和寶箱碰撞了嗎?(獲得寶物)
勇者和門(mén)碰撞了嗎?(成功逃出)
游戲成功與失敗的判定
然后,整理一下吧!
作為helper function集中起來(lái)由play function調用比較好的有
范圍限制(判斷是否在邊界)
碰撞判斷
那么,改變代碼吧
// 范圍限制(判斷是否在邊界)
// 這里放入的值是能夠移動(dòng)、移動(dòng)中的精靈(這里為勇者和怪物)和可移動(dòng)范圍
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;
};
// 雖然感覺(jué)沒(méi)必要獨立出來(lái),但還是和教程一樣分開(kāi)寫(xiě)吧
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
放入play function
function play() {
// 用鍵盤(pán)事件中獲取的數值來(lái)移動(dòng)
explorer.x += explorer.vx;
explorer.y += explorer.vy;
// 勇者的移動(dòng)范圍
contain(explorer, {x: 28, y: 10, width: 488, height: 480});
//contain(explorer, stage);
// 給勇者一個(gè)沒(méi)有碰撞的初始設定
var explorerHit = false;
// 使用怪物的分布
blobs.forEach(function(blob) {
// 使其移動(dòng)
blob.y += blob.vy;
// 給怪物一個(gè)移動(dòng)范圍的限制,捕捉返回的值
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)) {
// 做一個(gè)手持寶箱的樣子(寶箱的位置一直跟隨在勇者右下角)
treasure.x = explorer.x + 8;
treasure.y = explorer.y + 8;
}
// HP歸零則游戲結束,彈出失敗信息
if (healthBar.outer.width < 0) {
state = end;
message.text = "You lost!";
}
// 寶箱(和勇者)和門(mén)碰撞,游戲結束,彈出成功信息
if (hitTestRectangle(treasure, door)) {
state = end;
message.text = "You won!";
}
}
然后用end function切換場(chǎng)景
function end() {
gameScene.visible = false;
gameOverScene.visible = true;
}
游戲完成
補充:關(guān)于精靈
精靈除了坐標、visible、旋轉之外還有各種設置,詳情可以在官方文檔中查找。
連接如下
基本上精靈是遵循繼承規則的
DisplayObject > Container > Sprite
就此教程結束。
各位看官辛苦了。
本回的代碼:進(jìn)入git
本文翻譯自:2D WebGL renderer Pixi.js v4【連載第六回】、2D WebGL renderer Pixi.js v4【連載最終回】
翻譯者:廣樹(shù)
聯(lián)系客服