const GAME_END_TIME = 10 * 60; // 游戏结束时, 单位秒
const MIN_GAME_PLAYER_NUM = 2; // 最小游戏人数
const GAME_HALFTIME = 5; // 两局游戏间隔时间, 单位秒
const GROUND_Y = 9; // 地面的Y坐标
const PLAYER_HEIGHT = 4; // 玩家高度
const XZ_BEGIN = 1; // x和z坐标的开始值
const XZ_END = 127; // x和z坐标的结束值
const ZOMBIE_PROBABILITY = 0.1; // 僵尸出现几率
const HUMAN_DAMAGE = 10; // 人类武器伤害
const HUMAN_DISTANCE = 30; // 人类武器距离
const ZOMBIE_BEAT_BACK_FORCE = 3; // 僵尸被击退的力道
// 实体属性
const ENTITY_ATTRIBUTES = {
human: { // 人类的实体属性
hp: 100, // 血量
maxHp: 100, // 最大血量
enableDamage: false, // 关闭伤害
},
zombie: { // 僵尸的实体属性
hp: 1000,
maxHp: 1000,
enableDamage: true, // 关闭伤害
},
};
// 玩家属性
const PLAYER_ATTRIBUTES = {
human: { // 人类的玩家属性
color: new Box3RGBColor(1, 1, 1), // 色彩
walkSpeed: 0.22, // 走路最大速度
walkAcceleration: 0.19, // 走路加速度
runSpeed: 0.4, // 跑步最大速度
runAcceleration: 0.35, // 跑步加速度
jumpPower: 0.96, // 一段跳力量
doubleJumpPower: 0.9, // 二段跳力量
},
zombie: { // 僵尸的玩家属性
color: new Box3RGBColor(0, 1, 0),
walkSpeed: 0.4,
walkAcceleration: 0.3,
runSpeed: 0.8,
runAcceleration: 0.6,
jumpPower: 1.2,
doubleJumpPower: 1.1,
},
};
// 武器设置
const WEAPON = {
human: {
mesh: world.querySelector('.人类用的武器').mesh,
parts: [Box3BodyPart.RIGHT_HAND],
scale: new Box3Vector3(0.2, 0.3, 0.3), // 调整大小
orientation: new Box3Quaternion(0, 1, 0, 0).rotateX(Math.PI / 2).rotateY(-Math.PI / 2),
offset: new Box3Vector3(0, -0.45, 0), //调整位置
},
zombie: {
mesh: world.querySelector('.僵尸武器').mesh,
parts: [Box3BodyPart.LEFT_HAND, Box3BodyPart.RIGHT_HAND],
orientation: new Box3Quaternion(0, 1, 0, 0).rotateZ(-Math.PI / 2),
scale: new Box3Vector3(0.2, 0.2, 0.2),
offset: new Box3Vector3(0, -0.3, 0),
},
};
const spawnPoints = []; // 出生点坐标
const zombies = new Set();
const human = new Set();
let finalTeam; // 最终队伍
let playerScore; // 玩家分数
let gameEndResolve; // 用于结束游戏的函数
// 主函数
(async function main() {
// 打印开始时间的日志
console.log(`-------[${new Date().toISOString()}]--------`)
setup(); // 初始化
// 主循环, 不断检测并开始新的游戏
for(;;await sleep(GAME_HALFTIME * 1000)) {
// 检测人数
if (world.querySelectorAll('player').length < MIN_GAME_PLAYER_NUM) {
world.say(`当前人数不足${MIN_GAME_PLAYER_NUM}人, 无法开始游戏!`);
continue;
}
// 倒计时
world.say('游戏即将开始!');
for (let i = 0; i > 0; i--) {
await sleep(1000);
world.say(`${i}...`);
}
await start();
}
})();
// 开始游戏
async function start() {
const players = world.querySelectorAll('player');
// 初始化玩家分数
playerScore = { '': -1 };
finalTeam = {};
for (const entity of players) {
playerScore[entity.player.name] = 0;
}
// 计算僵尸数量
const zombieNum = Math.ceil(ZOMBIE_PROBABILITY * players.length);
for (let i = 0; i < zombieNum; i++) {
const entity = randomDelete(players); // 从玩家列表中随机删除一名玩家作为僵尸
setPlayer(entity, 'zombie');
setWeapon(entity, 'zombie');
zombies.add(entity);
finalTeam[entity.player.name] = 'zombie';
entity.player.directMessage('你乃丧尸!');
}
for (const entity of players) {
human.add(entity);
setWeapon(entity, 'human');
finalTeam[entity.player.name] = 'human';
entity.player.directMessage('你奶战士!');
}
await new Promise(async (resolve) => {
gameEndResolve = resolve; // 设置游戏结束函数
await sleep(GAME_END_TIME * 1000); // 等待GAME_END_TIME秒
resolve(); // 如果游戏没有提前结束, 就会由于到时间而结束
});
// 计算分数
const winner = zombies.size > human.size ? 'zombie' : 'human';
let best = '';
for (const name in playerScore) {
if (finalTeam[name] === winner && playerScore[name] > playerScore[best]) {
best = name;
}
}
const winnerName = winner === 'zombie' ? '僵尸' : '人类';
const winnerScoreName = winner === 'zombie' ? '感染数' : '击杀数';
world.say(`本局游戏${winnerName}获胜, ${best}以${playerScore[best]}的${winnerScoreName}成为MVP!`);
// 僵尸恢复人类
for (const entity of zombies) {
initPlayer(entity);
}
zombies.clear(); // 清空僵尸
}
// 游戏初始化
function setup() {
// 遍历地图, 寻找合适出生点
for (let x = XZ_BEGIN; x < XZ_END; x++) {
for (let z = XZ_BEGIN; z < XZ_END; z++) {
let i = 0;
while (i < PLAYER_HEIGHT && voxels.getVoxel(x, i + GROUND_Y, z) === 0) i++;
// 如果(x, GROUND_Y, z)有足够空间容纳玩家, 则将其纳入出生点
if (i === PLAYER_HEIGHT) {
spawnPoints.push([x, GROUND_Y + 2, z])
}
}
}
// 删除所有临时实体
world.querySelectorAll('.临时').forEach((e) => e.destroy());
// 初始化人类属性
world.onPlayerJoin(({entity}) => {
initPlayer(entity);
});
// 人类射击
world.onPress(({ entity, button, raycast: { hitEntity, direction, distance } }) => {
if (!(button === 'action0' && human.has(entity) && zombies.has(hitEntity))) return;
// 设置击中效果
hitEntity.velocity.x += direction.x * ZOMBIE_BEAT_BACK_FORCE;
hitEntity.velocity.y += 0.5;
hitEntity.velocity.z += direction.z * ZOMBIE_BEAT_BACK_FORCE;
// 计算伤害并应用
const basicDamage = Math.max(1, HUMAN_DAMAGE * Math.min(1, HUMAN_DISTANCE / distance));
const damage = Math.ceil(basicDamage * (1 + Math.random() * 0.3));
hitEntity.hurt(damage, { attacker: entity });
});
// 僵尸死亡
world.onDie(({entity, attacker}) => {
if (!(human.has(attacker) && zombies.has(entity))) return;
world.say(`@${attacker.player.name}击杀了僵尸${entity.player.name}.`);
playerScore[attacker.player.name]++; // 人类分数+1
attacker.player.directMessage(`击杀数: ${playerScore[attacker.player.name]}`);
zombies.delete(entity); // 移除僵尸
initPlayer(entity); // 恢复为人类, 可观战
if (zombies.size === 0) gameEndResolve(); // 判断是否还有僵尸, 否则结束游戏
});
// 玩家离开
world.onPlayerLeave(({entity}) => {
// 从僵尸或人类中移除离开的玩家, 并判断游戏是否结束
if (human.has(entity)) {
human.delete(entity);
if (human.size === 0) gameEndResolve();
} else if (zombies.has(entity)) {
zombies.delete(entity);
if (zombies.size === 0) gameEndResolve();
}
});
// 人类感染
world.onEntityContact(({entity, other}) => {
if (!(human.has(entity) && zombies.has(other))) return; // 只处理人类和僵尸碰撞
world.say(`@${entity.player.name}被${other.player.name}感染, 成为了新的僵尸.`);
playerScore[other.player.name]++; // 僵尸分数+1
playerScore[entity.player.name] = 0; // 人类分数清零
other.player.directMessage(`感染数: ${playerScore[other.player.name]}`);
removeWearable(entity); // 移除人类武器
setPlayer(entity, 'zombie'); // 设置僵尸属性
setWeapon(entity, 'zombie'); // 设置僵尸武器
finalTeam[entity.player.name] = 'zombie'; // 跟新最终阵营
zombies.add(entity); // 添加到僵尸列表
human.delete(entity); // 从人类列表中移除
if (human.size === 0) gameEndResolve(); // 判断是否还有人类, 否则结束游戏
});
}
// 移除装扮
function removeWearable(entity) {
WEAPON.zombie.parts.forEach((part) => {
const wearables = entity.player.wearables(part); // 查找所有可穿戴物品
for (const wearable of wearables) {
entity.player.removeWearable(wearable); // 移除可穿戴物品
}
});
}
// 装备武器
function setWeapon(entity, camp) {
WEAPON[camp].parts.forEach((part) => entity.player.addWearable({
bodyPart: part, // 装备在右手上
mesh: WEAPON[camp].mesh, // 设置武器mesh
orientation: WEAPON[camp].orientation, // 设置方向
scale: WEAPON[camp].scale, // 设置大小
offset: WEAPON[camp].offset, // 设置位置
}));
}
// 初始化玩家为人类
function initPlayer(entity) {
entity.position.set(...randomPick(spawnPoints)); // 随机位置
// entity.player.cameraMode = 'FPS'; // 设置FPS模式
setPlayer(entity, 'human'); // 初始化为人类属性
removeWearable(entity); // 移除已装备的武器
}
// 设置玩家为人类或僵尸
function setPlayer(entity, camp = 'human') {
Object.assign(entity, ENTITY_ATTRIBUTES[camp]); // 复制实体属性
Object.assign(entity.player, PLAYER_ATTRIBUTES[camp]); // 复制玩家属性
}
// 随机数组下标
function randomIndex(arr) {
return Math.floor(Math.random() * arr.length);
}
// 随机选取数组中的元素
function randomPick(arr) {
return arr[randomIndex(arr)];
}
// 随机删除数组中的元素
function randomDelete(arr) {
return arr.splice(randomIndex(arr), 1)[0];
}