前情提要
在上一节中,我们实现了碰撞检测功能。现在是时候添加一些敌人,让游戏变得更有挑战性了。
设计敌人的数据结构
和角色一样,敌人也需要一个对象来存储它的属性:
const enemy = {
image: new Image(),
x: 500,
y: floorY,
moveSpeed: 50, // 敌人的移动速度
direction: 0, // 移动方向:0 表示向左,1 表示向右
isAlive: true, // 是否存活
};
enemy.image.src = "./images/bad_guy.png";
绘制敌人
在 drawImg 函数中添加敌人的绘制:
function drawImg() {
// ... 其他绘制代码 ...
// 绘制敌人
if (enemy.isAlive) {
ctx.drawImage(
enemy.image,
enemy.x - enemy.image.width / 2,
enemy.y - enemy.image.height / 2
);
}
// 绘制角色
ctx.drawImage(
player.image,
player.x - player.image.width / 2,
player.y - player.image.height / 2
);
}
让敌人也受重力影响
敌人也应该受到重力的影响,我们可以把之前的 gravityController 函数改造成通用的:
function gravityController(entity, modifier) {
entity.y -= entity.ySpeed * modifier;
if (entity.y < floorY) {
entity.ySpeed -= g * modifier;
} else {
entity.ySpeed = 0;
entity.y = floorY;
if (entity.airCount !== undefined) {
entity.airCount = 0;
}
}
}
然后在 update 函数中同时应用到角色和敌人:
function update(modifier) {
// ... 其他更新逻辑 ...
// 对角色和敌人都应用重力
gravityController(player, modifier);
if (enemy.isAlive) {
gravityController(enemy, modifier);
}
}
不过,敌人需要有 ySpeed 属性:
const enemy = {
image: new Image(),
x: 500,
y: floorY,
moveSpeed: 50,
direction: 0,
isAlive: true,
ySpeed: 0, // 新增:垂直速度
};
enemy.image.src = "./images/bad_guy.png";
检测角色和敌人的碰撞
现在我们需要检测角色和敌人是否碰撞:
function isCollision(entity1, entity2) {
const xDistance = Math.abs(entity1.x - entity2.x);
const yDistance = Math.abs(entity1.y - entity2.y);
const width1 = entity1.image ? entity1.image.width : entity1.width;
const height1 = entity1.image ? entity1.image.height : entity1.height;
const width2 = entity2.image ? entity2.image.width : entity2.width;
const height2 = entity2.image ? entity2.image.height : entity2.height;
const xOverlap = xDistance <= (width1 + width2) / 2;
const yOverlap = yDistance <= (height1 + height2) / 2;
return xOverlap && yOverlap;
}
判断是否踩到敌人
在马里奥游戏中,如果角色从上方踩到敌人,敌人会被消灭。我们需要一个函数来判断是否是"踩"的动作:
function isStampOn(player, enemy) {
// 如果角色的底部位置高于敌人的中心位置,说明是从上方踩下来的
const playerBottom = player.y + (player.image.height / 2);
const enemyCenter = enemy.y;
return playerBottom < enemyCenter;
}
处理碰撞逻辑
在 update 函数中添加碰撞处理:
function update(modifier) {
// ... 其他更新逻辑 ...
gravityController(player, modifier);
if (enemy.isAlive) {
gravityController(enemy, modifier);
}
// 检测角色和敌人的碰撞
if (enemy.isAlive && isCollision(player, enemy)) {
if (isStampOn(player, enemy)) {
// 踩到敌人,敌人死亡
enemy.isAlive = false;
player.ySpeed = 200; // 给角色一个向上的反弹
console.log("踩死敌人!");
} else {
// 被敌人撞到,游戏结束
console.log("被敌人撞到了!游戏结束!");
// 这里可以添加游戏结束的逻辑
}
}
}
敌人重生
当敌人被踩死后,我们可以让它在一段时间后重生:
function update(modifier) {
// ... 其他更新逻辑 ...
// 检测角色和敌人的碰撞
if (enemy.isAlive && isCollision(player, enemy)) {
if (isStampOn(player, enemy)) {
enemy.isAlive = false;
player.ySpeed = 200;
// 1 秒后重生
setTimeout(() => {
enemy.isAlive = true;
// 随机重生位置
enemy.x = Math.random() * cvWidth;
enemy.y = floorY;
}, 1000);
} else {
console.log("被敌人撞到了!游戏结束!");
}
}
}
添加得分系统
每次踩到敌人,增加分数:
const gameData = {
currentTimeStamp: new Date().getTime(),
score: 0, // 新增:分数
};
在踩到敌人时增加分数:
if (isStampOn(player, enemy)) {
enemy.isAlive = false;
player.ySpeed = 200;
gameData.score++; // 增加分数
setTimeout(() => {
enemy.isAlive = true;
enemy.x = Math.random() * cvWidth;
enemy.y = floorY;
}, 1000);
}
在 drawImg 函数中显示分数:
function drawImg() {
// ... 其他绘制代码 ...
ctx.font = "20px 微软雅黑";
ctx.fillStyle = "#fff";
ctx.fillText("得分: " + gameData.score, 10, 30);
ctx.fillText("按空格跳跃,A/D 左右移动", 10, 60);
ctx.fillText("踩敌人得分!", 10, 90);
// ... 其他绘制代码 ...
}
小结
这节课我们学习了:
- 如何添加敌人角色
- 如何让敌人也受重力影响
- 如何判断是否踩到敌人
- 如何实现敌人重生
- 如何添加得分系统
在下一节课中,我们会给敌人添加 AI,让它能够自动移动。