最近在家闲着无事利用Phaser引擎是开发了一款弓箭射击小游戏。废话少说先看效果:
在线体验:
1、游戏的主要逻辑实现:
GameState.js:
export default class GameState extends Phaser.State {
init() {
this.eagle = null; // 雄鹰,每1000分出现一次
this.birds = []; // 飞鸟集合
this.score = 0; // 得分
this.arrows = []; // arrow集合
this.scoreText = null; // 得分
this.bowArrow = null; // 底部弓箭
this.backButton = null; // 返回按钮
// 计算arrow飞行速度
this.arrowSpeed = windowHeight / 1600;
this.createBirdsIntervalId = 0;
this.recoverBirdsIntervalId = 0;
this.durationChangeIntervalId = 0;
this.collisionDetecIntervalId = 0;
},
preload(){
},
create(){
},
render(){},
update(){}
}
2、在preload中添加背景图片、返回按钮和弓箭图片:
preload() {
let that = this;
let game = this.game;
// 创建背景图图片
let gameBgImg = game.add.image(0, 0, "gamebg1");
gameBgImg.width = windowWidth;
gameBgImg.height = windowHeight;
// 弓箭图片
var bowArrow = game.add.image(windowWidth / 2, windowHeight - 80, "bowarrow");
this.bowArrow = bowArrow;
bowArrow.anchor.set(0.5);
bowArrow.scale.set(0.38);
bowArrow.inputEnabled = true;
var backImg = game.add.image(25, 25, "back");
backImg.anchor.set(0.5);
backImg.scale.set(0.8);
this.backButton = backImg;
// 总分数
var scoreText = game.add.text(windowWidth - 140, 25, "得分:0", {
font: "20px",
fill: "#f7db04",
align: "center",
stroke: "#f8abc5",
strokeThickness: 0.5
})
this.scoreText = scoreText;
this.scoreText.anchor.set(0,0.5)
// 将得分和返回按钮置顶
setInterval(() => {
backImg.bringToTop();
scoreText.bringToTop();
}, 1);
}
3、通过setInterval向屏幕中添加飞鸟(这里的所有动画都未使用物理引擎,还待进一步研究,下面所有代码同理):
this.createBirdsIntervalId = setInterval(() => {
// 添加飞鸟
for (var i = 0; i < MAX_BIRD_NO - this.birds.length; i++) {
var birdKind = BIRD_KINDS[Math.floor(Math.random() * MAX_BIRD_NO)];
var kindName = birdKind["name"];
var from = birdKind["from"];
var scale = birdKind["scale"];
var price = birdKind["price"];
var bird = null;
var duration = DURATIONS[Math.floor(Math.random() * 3)] + Math.random() * 2000;
// 从左到右
if (from == "left") {
bird = this.game.add.sprite(-80 - Math.random() * windowWidth, 40 + Math.ceil(Math.random() * windowHeight * 0.5), kindName);
bird.animations.add('fly', null, 8, true);
bird.animations.play('fly', 8, true, true);
this.game.add.tween(bird).to({
x: windowWidth + 80
}, duration, "Linear", true, 0, -1, false);
} else { // 从右到左
bird = this.game.add.sprite(windowWidth * (1 + Math.random()) + 80, 40 + Math.ceil(Math.random() * windowHeight * 0.45), kindName);
bird.animations.add('fly', null, 8, true);
bird.animations.play('fly', 8, true, true);
this.game.add.tween(bird).to({
x: -80
}, duration, "Linear", true, 0, -1, false);
}
bird.anchor.set(0.5);
bird.scale.set(scale);
bird.zoom = scale;
bird.name = kindName;
bird.price = price;
bird.from = from;
bird.diedPic = birdKind["diedPic"];
this.birds.push(bird);
}
}, 0);
4、飞鸟飞出屏幕回收飞鸟对象:
this.recoverBirdsIntervalId = setInterval(() => {
// 如果飞鸟飞出屏幕销毁飞鸟对象
for (var i = 0; i < this.birds.length; i++) {
var bird = this.birds[i];
if (this.birdHasFlyAway(bird)) {
this.birds.splice(i, 1)
i--;
bird.destroy();
}
}
}, 0);
5、碰撞检测:
this.collisionDetecIntervalId = setInterval(() => {
// 检测碰撞
for (var i = 0; i < this.arrows.length; i++) {
var arrow = this.arrows[i];
// 如果箭矢超出屏幕,销毁
if (arrow.x - windowWidth - arrow.height / 4 > 0 ||
arrow.x < -arrow.height / 2 + 10 ||
arrow.y < -arrow.height / 2 + 10) {
this.arrows.splice(i, 1);
i--;
arrow.destroy();
continue;
}
if (this.eagle && this.collisionDetec(this.eagle, arrow)) {
this.eagle.shoted++;
this.arrows.splice(i, 1);
i--;
arrow.destroy();
if (this.eagle.shoted == 1) { // 雄鹰第一次被射中开始发起攻击
utils.playAudio("assets/audio/eagle.ogg");
this.attack();
} else if (this.eagle.shoted == 3) { // 如果被射中三次雄鹰被击杀
this.eagle.diedPic = this.eagle.x < windowWidth / 2 ? "eagle003" : "eagleback003";
this.eagle.name = this.eagle.x < windowWidth / 2 ? "eagle" : "eagleback";
this.eagle.zoom = EAGLE_SCALE;
utils.playAudio("assets/audio/eagledied.ogg");
this.createDiedStatus(this.eagle, 100, 5);
this.eagle.destroy();
this.eagle = null;
this.createShowComboText("击杀雄鹰 +120");
this.changeScoreText(EAGLE_SCORE);
}
continue;
}
for (var j = 0; j < this.birds.length; j++) {
var b = this.birds[j]
if (this.collisionDetec(b, arrow)) {
arrow.kiledBirdsNo++;
this.birds.splice(j, 1);
j--;
b.destroy();
this.killBird(b);
// b.animations.stop();
if (b.name == "sgbird") {
this.arrows.splice(i, 1);
i--;
arrow.destroy();
break;
}
}
}
}
}, 0);
6、响应gameState关闭事件:将所有interval删除并解绑touch事件(从第2段代码到本段代码聚在preload中):
this.state.onShutDownCallback = function () {
clearInterval(that.createBirdsIntervalId);
clearInterval(that.recoverBirdsIntervalId);
clearInterval(that.durationChangeIntervalId);
clearInterval(that.collisionDetecIntervalId);
that.offTouchEvents();
}
7、绑定touchStart,touchMove,touchEnd事件:
create() {
let game = this.game;
let that = this;
/**
* 计算弓箭偏移角度
*/
function calAngle(x, y) {
if (y >= 0) {
if (x > 0) {
return 90;
}
if (x < 0) {
return -90;
}
}
var z = Math.sqrt(x * x + y * y);
return Math.asin(x / z) * 180 / Math.PI;
}
wx.onTouchStart(touchStartCallback = (event) => {
// 如果按下返回按钮返回到导航页面
var x = event.changedTouches[0].clientX;
var y = event.changedTouches[0].clientY;
if (x >= that.backButton.x - 20 && x <= that.backButton.x + 20 &&
y >= that.backButton.y - 20 && y <= that.backButton.y + 20) {
if (!that.gameIsOver()) {
wx.showModal({
title: "游戏提醒",
content: "游戏尚未结束是否退出?",
cancelColor: 'red',
success: function (res) {
if (res.confirm) {
that.updateRanks();
game.state.start("NavState");
that.shutdown();
}
}
})
} else {
game.state.start("NavState");
that.shutdown();
}
return;
}
that.bowArrow.angle = calAngle(x - that.bowArrow.x, y - that.bowArrow.y);
});
wx.onTouchMove(touchMoveCallback = (event) => {
var x = event.changedTouches[0].clientX;
var y = event.changedTouches[0].clientY;
that.bowArrow.angle = calAngle(x - that.bowArrow.x, y - that.bowArrow.y);
});
wx.onTouchEnd(touchEndCallback = (event) => {
var x = event.changedTouches[0].clientX;
var y = event.changedTouches[0].clientY;
for (var i = 0; i < that.arrows.length; i++) {
var arrow = that.arrows[i];
// 如果箭矢超出2.2秒未销毁,销毁
if(new Date().getTime() - arrow.shootTime >= 2200){
that.arrows.splice(i,1);
i--;
arrow.destroy();
}
}
that.bowArrow.angle = calAngle(x - that.bowArrow.x, y - that.bowArrow.y);
// arrow在同一时刻只能有两个
if (that.arrows.length >= 2) return;
if (!canShoot) return;
canShoot = false;
setTimeout(()=>{
canShoot = true;
},SHOOT_DURATION);
utils.playAudio("assets/audio/shoot.ogg");
// 发射箭矢
var arrow = game.add.image(windowWidth / 2, windowHeight - 80, "arrow");
arrow.shootTime = new Date().getTime();
arrow.anchor.set(0.5);
arrow.scale.set(0.38);
arrow.angle = that.bowArrow.angle;
// 计算箭矢终点坐标(toX,toY)
var rightAngle = calAngle(windowWidth - that.bowArrow.x, -that.bowArrow.y);
var leftAngle = -rightAngle;
var toX = -arrow.height / 2;
var toY = -arrow.height / 2;
if (arrow.angle >= leftAngle && arrow.angle <= rightAngle) {
toX = Math.tan(-arrow.angle * Math.PI / 180) * (toY - arrow.y) + arrow.x;
} else if (arrow.angle > rightAngle) {
toX = windowWidth - toX;
}
toY = (toX - arrow.x) / Math.tan(-arrow.angle * Math.PI / 180) + arrow.y;
var $z = Math.sqrt(Math.pow(toX - arrow.x, 2) + Math.pow(toY - arrow.y, 2));
game.add.tween(arrow).to({
x: toX,
y: toY
}, $z / that.arrowSpeed, "Linear", true, 0, -1, false);
arrow.kiledBirdsNo = 0;
arrow.events.onDestroy.add(() => {
var text = null;
var score = TWO_BIRDS_SCORE;
if (arrow.kiledBirdsNo == 2) {
// 一箭双雕
text = "一箭双雕 +" + TWO_BIRDS_SCORE;
}
if (arrow.kiledBirdsNo == 3) {
// 一石三鸟
text = "一石三鸟 +" + THREE_BIRDS_SCORE;
score = THREE_BIRDS_SCORE;
}
if (text) {
utils.playAudio("assets/audio/combo.ogg");
this.createShowComboText(text);
this.changeScoreText(score);
}
});
that.arrows.push(arrow);
})
}
8、创建雄鹰对象并判断游戏是否结束:
update() {
let that = this;
// 每增加500分出现一次雄鹰,同时一个屏幕中只能出现一个
if (this.score >= 500 && this.score % 500 < EAGLE_SCORE && !this.eagle) {
var eagleFrom = ["right", "left"][Math.floor(Math.random() * 2)];
var x = -80 - Math.random() * windowWidth,
y = 40 + Math.ceil(Math.random() * windowHeight * 0.45);
var toX = windowWidth + 80,
toY = 40 + Math.ceil(Math.random() * windowHeight * 0.45);
if (eagleFrom == "right") {
x = windowWidth * (1 + Math.random()) + 80;
toX = -80;
}
this.createNewEagle(eagleFrom, x, y, toX, toY);
} else if (this.eagle) { // 雄鹰一直存在于屏幕中除非被击杀
if ((this.eagle.from == "right" && this.eagle.x <= 20) ||
(this.eagle.from == "left" && this.eagle.x >= windowWidth - 20)) {
var from = this.eagle.from == "left" ? "right" : "left";
var x = this.eagle.x,
y = this.eagle.y;
var toX = windowWidth - 20,
toY = toY = 40 + Math.ceil(Math.random() * windowHeight * 0.45);;
if (from == "right") {
toX = 20;
}
this.createNewEagle(from, x, y, toX, toY);
}
}
if (this.gameIsOver()) {
utils.playAudio("assets/audio/gameover.ogg");
var extText = this.updateRanks();
this.eagle.destroy();
this.eagle = null;
// console.log("gameOver");
wx.showModal({
title: "游戏提示",
content: "Game Over\r\n" + that.scoreText.text + extText,
cancelColor: 'red',
confirmText: "新游戏",
cancelText: "退出",
confirmColor: "blue",
success: (res) => {
that.offTouchEvents();
if (res.confirm) {
that.state.restart("GameState");
} else {
that.state.start("NavState");
}
}
});
}
}
9、相关辅助函数
createNewEagle(from, x, y, toX, toY) {
var duration = DURATIONS[Math.floor(Math.random() * 3)] / 2.5 + Math.random() * 2000;
var tempEagle = this.eagle;
this.eagle = this.game.add.sprite(x, y, from == "left" ? "eagle" : "eagleback");
this.eagle.animations.add('fly', null, 8, true);
this.eagle.animations.play('fly', 8, true, true);
this.eagle.anchor.set(0.5);
this.eagle.scale.set(EAGLE_SCALE);
this.eagle.from = from;
this.game.add.tween(this.eagle).to({
x: toX,
y: toY
}, duration, "Linear", true, 0, -1, false);
if (tempEagle) {
this.eagle.shoted = tempEagle.shoted;
tempEagle.destroy();
} else {
this.eagle.shoted = 0;
}
}
render() {
}
// 碰撞检测
collisionDetec(bird, arrow) {
// 箭矢消失,取消碰撞检测
if (!arrow) return false;
var birdStartPointer = {};
var birdEndPointer = {};
birdStartPointer.x = bird.x - bird.width / 2 + 5;
birdStartPointer.y = bird.y - 8;
birdEndPointer.x = bird.x + bird.width / 2 - 5;
birdEndPointer.y = bird.y + 8;
var arrowHalfHighet = arrow.height / 2 - 3;
var offsetX = arrowHalfHighet * Math.sin(arrow.angle * Math.PI / 180);
var offsetY = arrowHalfHighet * Math.cos(arrow.angle * Math.PI / 180);
var arrowHeadPointer = {};
arrowHeadPointer.x = arrow.x + offsetX;
arrowHeadPointer.y = arrow.y - offsetY;
// 如果箭头超出屏幕范围,取消碰撞检测
if (arrowHeadPointer.x < 0 || arrowHeadPointer.x > windowWidth ||
arrowHeadPointer.y > windowHeight - 80 ||
arrowHeadPointer.y < 0) {
return false;
}
if (arrowHeadPointer.x >= birdStartPointer.x && arrowHeadPointer.x <= birdEndPointer.x &&
arrowHeadPointer.y >= birdStartPointer.y && arrowHeadPointer.y <= birdEndPointer.y) {
utils.playAudio("assets/audio/hited.ogg");
return true;
} else {
return false;
}
}
attack() {
var eagleInLeft = windowWidth / 2 > this.eagle.x;
var targetX = this.bowArrow.x,
targetY = this.bowArrow.y; // 攻击点为底部弓箭中点坐标
// 从高处发起攻击,默认在左侧被射中
var prepareToX = 20 + (windowWidth / 2 - 20) * Math.random(),
prepareToY = this.eagle.y * Math.random();
var name = "eaglepreattack",
attackPic = "eagle005";
// 如果在右侧被射中
if (!eagleInLeft) {
prepareToX = windowWidth / 2 + (windowWidth / 2 - 20) * Math.random();
name = "eaglebackpreattack";
attackPic = "eagleback005"
}
var te = this.eagle;
this.eagle = this.game.add.sprite(this.eagle.x, this.eagle.y, name);
this.eagle.anchor.set(0.5);
this.eagle.scale.set(EAGLE_SCALE);
this.eagle.animations.add('fly', null, 8, true);
this.eagle.animations.play('fly', 8, true, true);
this.eagle.shoted = te.shoted;
this.eagle.from = te.from;
te.destroy();
var offsetX = (prepareToX - this.eagle.x) / 100;
var offsetY = (prepareToY - this.eagle.y) / 100;
var times = 0;
let that = this;
this.eagle.intervalId = setInterval(() => {
times++;
that.eagle.x += offsetX;
that.eagle.y += offsetY;
if (times > 100) {
var tempEagle = that.eagle;
that.eagle = that.game.add.sprite(that.eagle.x, that.eagle.y, name, attackPic);
this.eagle.anchor.set(0.5);
this.eagle.scale.set(EAGLE_SCALE);
that.eagle.shoted = tempEagle.shoted;
that.eagle.intervalId = tempEagle.intervalId;
that.eagle.from = tempEagle.from;
that.game.add.tween(that.eagle).to({
x: targetX,
y: targetY
}, EAGLE_DURATIONS, "Linear", true, 0, -1, false);
tempEagle.destroy();
clearInterval(that.eagle.intervalId);
}
}, 5);
}
createDiedStatus(bird, duration, times) {
// 创建小鸟死亡图像
let that = this;
var diedImg = that.game.add.sprite(bird.x, bird.y, bird.name, bird.diedPic);
diedImg.anchor.set(0.5);
diedImg.scale.set(bird.zoom);
// 尸体闪烁
var tintChangedTimes = 0;
diedImg.intervalId = setInterval(() => {
// console.log("titmer...");
tintChangedTimes++;
diedImg.tint = Math.random() * 0xffffff;
if (tintChangedTimes > times) {
clearInterval(diedImg.intervalId);
diedImg.destroy();
}
}, duration);
}
// 判断飞鸟是否飞出屏幕
birdHasFlyAway(bird) {
if (bird.from == "right") {
if (bird.x + 80 <= 1) {
return true;
}
} else {
if (windowWidth + 80 - bird.x <= 1) {
return true;
}
}
return false;
}
changeScoreText(price) {
this.score = parseInt(this.scoreText.text.replace("得分:", "")) + price;
this.scoreText.text = "得分:" + JSON.stringify(this.score);
}
gameIsOver() {
if (!this.eagle) return false;
var x = this.eagle.x,
y = this.eagle.y + 20;
var bowArrowStartPointer = {
x: this.bowArrow.x - this.bowArrow.width / 2,
y: this.bowArrow.y - 10
};
var bowarrowEndPointer = {
x: this.bowArrow.x + this.bowArrow.width / 2,
y: this.bowArrow.y + 10
};
return x >= bowArrowStartPointer.x && x <= bowarrowEndPointer.x &&
y >= bowArrowStartPointer.y && y <= bowarrowEndPointer.y;
}
killBird(bird) {
let that = this;
// 如果小鸟超出屏幕范围不计分
if (bird.x < -bird.width / 2 || bird.x > windowWidth + bird.width / 2) {
return;
}
// console.log("bird " + bird.name + " was killed.....");
// console.log("bird width/2:" + bird.width / 2);
// 创建加分text
var priceText = that.game.add.text(bird.x, bird.y - 10, "+ " + bird.price, {
font: "20px",
fill: "#00FF00",
align: "center",
stroke: "#d4237a",
strokeThickness: 1
});
// 250毫秒内消失,击杀分数
priceText.anchor.set(0.5);
var priceTextChangedTimes = 0;
priceText.intervalId = setInterval(() => {
priceText.y -= 2;
if (priceTextChangedTimes > 10) {
clearInterval(priceText.intervalId)
priceText.destroy();
}
priceTextChangedTimes++;
}, 50);
this.createDiedStatus(bird, 100, 5);
this.changeScoreText(bird.price);
}
createShowComboText(text) {
var showTextChangedTimes = 0;
var showText = this.game.add.text(windowWidth / 2, windowHeight / 2, text, {
font: "22px",
fill: "#f00b59",
align: "center",
stroke: "#f8abc5",
strokeThickness: 0.5
});
showText.anchor.set(0.5);
showText.intervalId = setInterval(() => {
showText.y -= 1.5;
if (showTextChangedTimes > 35) {
clearInterval(showText.intervalId);
showText.destroy();
}
showTextChangedTimes++;
}, 25);
}
updateRanks() {
if (!this.score) return 0;
var ranks = wx.getStorageSync('ranks');
var len = ranks.length;
var i = 0;
var item = {
time: utils.formatTime(new Date()),
score: this.score
};
while (i < len && ranks[i].score > this.score) {
i++;
}
if (i == len && len < 10) {
ranks.push(item);
} else {
ranks.splice(i, 0, item);
}
if (ranks.length > 10) ranks.splice(10, ranks.length - 10);
wx.setStorageSync('ranks', ranks);
return i == 0 ? "\r\n新的最高分:" + this.score : "";
}
offTouchEvents() {
wx.offTouchStart(touchStartCallback);
wx.offTouchMove(touchMoveCallback);
wx.offTouchEnd(touchEndCallback);
}
至此小游戏主要逻辑就万成了。喜欢的话可以扫码体验一下哦。