一个神器代码岛的枪战代码
console.clear()
console.clear()
// ------------配置------------
const DEFAULT_WEAPON = '步枪'; // 初始武器
// 队伍配置
const TEAM_CONFIG = {
'蓝队': {
spawnPoint: '蓝队出生点', // 出生点实体名称
color: new GameRGBColor(1, 0, 0), // 颜色
},
'黄队': {
spawnPoint: '黄队出生点',
color: new GameRGBColor(1, 1, 0),
},
};
// 武器配置
const WEAPON = {
'散弹枪': {
damage: 30, // 伤害
distance: 10, // 射程距离
interval: 30, // 发射间隔(数值越大, 射速越慢)
sound: `audio/霰弹枪.mp3`,
},
'步枪': {
damage: 10,
distance: 30,
interval: 5,
sound: `audio/步枪.mp3`,
},
'狙击枪': {
damage: 25,
distance: 50,
interval: 40,
sound: `audio/狙击枪.mp3`,
},
};
// 头部穿戴配置
const HEAD_WEARABLE_CONFIG = {
bodyPart: GameBodyPart.HEAD,
orientation: new GameQuaternion(0, 0, 0, 1), // 调整方向
scale: new GameVector3(0.7, 0.7, 0.7), // 调整大小
offset: new GameVector3(0, 0.2, 0), //调整位置
};
// 武器穿戴配置
const WEAPON_WEARABLE_CONFIG = {
bodyPart: GameBodyPart.TORSO,
orientation: new GameQuaternion(0, 0, 0, 1), // 装备方向
scale: new GameVector3(0.9, 0.9, 0.9), // 调整大小
offset: new GameVector3(0, 0.1, 0.9), //调整位置
};
// 枪口火焰配置
const MUZZLE_FLASH_CONFIG = {
bodyPart: GameBodyPart.TORSO,
orientation: new GameQuaternion(0, 0, 0, 1), // 装备方向
scale: new GameVector3(0.9, 0.9, 0.9), // 调整大小
offset: new GameVector3(-0.2, 0.3, 2.1), //调整位置
mesh: `mesh/火花效果.vb`
};
const Quat = new GameQuaternion(0,0,0,1);
const GAME_END_TIME = 60; // 游戏结束时间, 单位秒
const GAME_END_SCORE = 10; // 游戏结束分数
const MIN_GAME_PLAYER_NUM = 2; // 最小游戏人数
const GAME_HALFTIME = 5; // 两局游戏间隔时间, 单位秒
// ----------------------------
const spawnPoint = objectMap(TEAM_CONFIG, ({spawnPoint}, team) => {
const e = world.querySelector('#' + spawnPoint);
return e.position.clone(); // 返回出生点位置
});
const defaultWeaponMesh = world.querySelector('#' + DEFAULT_WEAPON).mesh;
let playerState = {}; // 玩家状态
let teamScore = {}; // 队伍分数
let teamNum = {}; // 队伍人数
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() {
console.log(`[${new Date().toISOString()}]开始新的一局游戏`);
// 清空分数和队伍人数
teamScore = objectMap(TEAM_CONFIG, () => 0);
teamNum = objectMap(TEAM_CONFIG, () => 0);
// 获取队伍名称
const teamNames = Object.keys(TEAM_CONFIG);
// 打乱所有玩家并执行队伍分配
arrayShuffle(world.querySelectorAll('player')).forEach((entity, i) => {
const teamName = teamNames[i % teamNames.length];
joinTeam(entity, teamName);
entity.player.directMessage(`你被分配到了${teamName}`);
});
// 等待游戏结束
await new Promise(async (resolve) => {
gameEndResolve = resolve; // 设置游戏结束函数
await sleep(GAME_END_TIME * 1000); // 等待GAME_END_TIME秒
resolve(); // 如果游戏没有提前结束, 就会由于到时间而结束
});
// 广播获胜队伍
const winner = getMaxScore();
world.say(`${winner}胜利, 总分: ${teamScore[winner]}`);
let maxKd = 0; // 最高的kd
let maxKdPlayer; // 最高kd的玩家
// 计算每个玩家的KD并记录最高值
for (const name in playerState) {
const { entity, k, d } = playerState[name];
const kd = k / (d + 1); // 计算公式: 击杀数/(死亡数+1)
// 告知每个玩家本局的kd
entity.player.directMessage(`你本场游戏的评分为${kd.toFixed(2)}`);
// 更新最大kd
if (kd > maxKd) {
maxKd = kd;
maxKdPlayer = name;
}
// 把玩家设置为非游戏状态
entity.enableDamage = false;
entity.player.color.set(1, 1, 1);
removeSuit(entity);
}
// 广播MVP(kd最高者)
world.say(`本场游戏MVP为@${maxKdPlayer}, 评分是${maxKd.toFixed(2)}`);
// 清空玩家状态和结束函数
playerState = {};
gameEndResolve = undefined;
}
// 加入队伍
function joinTeam(entity, team) {
teamNum[team]++; // 队伍人数+1
// 设置玩家属性
entity.enableDamage = true;
// 初始化玩家状态
playerState[entity.player.name] = {
team,
entity,
weapon: DEFAULT_WEAPON,
shootTick: 0,
k: 0,
d: 0,
};
// 装备武器
wearSuit(entity, team, DEFAULT_WEAPON);
// 设置出生点, 随机增加y的偏移, 避免多个玩家相撞
const point = spawnPoint[team].add(new GameVector3(0, 10 + Math.random() * 20, 0));
entity.player.spawnPoint.copy(point);
entity.player.forceRespawn(); // 传送到队伍的出生点
}
// 判断是否是游戏中的玩家
function isPlayer(entity) {
return entity && entity.isPlayer && entity.player.name in playerState;
}
// 获取最高分的队伍名称
function getMaxScore() {
let num = 0;
let team;
for (const key in teamScore) {
if (teamScore[key] > num) {
team = key;
num = teamScore[key];
}
}
return team;
}
// 获取最低分的队伍名称
function getMinTeam() {
let num = Infinity;
let team;
for (const key in teamNum) {
if (teamNum[key] < num) {
team = key;
num = teamNum[key];
}
}
return team;
}
// 需要隐藏的身体部件
let invisiblePart = ['head', 'torso', 'leftUpperArm', 'leftLowerArm', 'leftHand', 'rightUpperArm', 'rightLowerArm', 'rightHand'];
// 玩家部件穿戴
function wearSuit(entity, team, weapon) {
// 隐藏指定身体部件
for (const bodyPart of invisiblePart) {
entity.player.skinInvisible[bodyPart] = true;
}
// 头部穿戴
entity.player.addWearable({...HEAD_WEARABLE_CONFIG, mesh:`mesh/${team}头部.vb`});
// 身体穿戴
entity.player.addWearable({...WEAPON_WEARABLE_CONFIG, mesh:`mesh/${team}持枪胸部${weapon}.vb`});
}
// 玩家部件移除
function removeSuit(entity) {
for (const bodyPart of invisiblePart) {
entity.player.skinInvisible[bodyPart] = false;
}
for (const wearble of entity.player.wearables()) {
entity.player.removeWearable(wearble);
}
}
// 发射子弹
async function fireBullet(position, direction) {
const dir = direction.clone()
let dist = Math.sqrt(dir.x * dir.x + dir.z * dir.z)
const rotx = Math.atan2(dir.y, dist)
// 子弹模型本身需要旋转Math.PI / 2,在这个基础上围绕X与Y轴旋转到指定方向
let result = Quat.rotateY(Math.PI / 2).rotateX(rotx).rotateY(Math.atan2(dir.z, dir.x))
// 创建子弹实体
world.createEntity({
mesh: `mesh/子弹.vb`,
tags: [`bullet`],
position: position.add(new GameVector3(0, 0.65, 0)),
collides: true,
gravity: false,
meshScale: new GameVector3(1 / 32 , 1 / 32 , 1 / 32 ),
meshOrientation: result,
velocity: dir.scale(4), // 设置子弹飞行速度
})
}
// 展示枪口火焰(并隐藏)
async function showMuzzleFlash(entity) {
const wearble = entity.player.addWearable(MUZZLE_FLASH_CONFIG);
await sleep(100);
entity.player.removeWearable(wearble);
}
// 初始化
function setup() {
// 处理死亡
world.onDie(async({entity, attacker}) => {
if (!attacker || !isPlayer(entity)) return; // 不处理出无击杀者的死亡和非玩家的死亡
// 获取死亡玩家名称和击杀者名称
const { name } = entity.player;
const killerName = attacker.isPlayer ? attacker.player.name : attacker.id;
playerState[name].d++; // 死亡数+1
// 如果击杀者是玩家
if (attacker.isPlayer) {
playerState[killerName].k++; // 击杀数+1
teamScore[playerState[killerName].team]++; // 队伍分数+1
// 广播死亡情况
world.say(`@${killerName}击败了@${name}, 当前比分${Object.keys(teamScore).map((name) => `${name}:${teamScore[name]}`).join(', ')}`);
// 判断是否达到比赛结束的比分
if (teamScore[playerState[killerName].team] >= GAME_END_SCORE) gameEndResolve();
}
//3秒后复活
await sleep(3 * 1000)
if (entity.player.dead) {
entity.player.forceRespawn()
}
});
// 初始化玩家
world.onPlayerJoin(({ entity }) => {
if (gameEndResolve) {
// 游戏已经开始, 则加入人数组少的队伍
const team = getMinTeam()
joinTeam(entity, team);
world.say(`玩家@${entity.player.name}加入${team}`);
}
// 处理玩家鼠标点击
entity.player.onPress(({ tick, entity, button, raycast: { hitEntity, direction, distance } }) => {
if (entity.player.dead) return
// 判断是否是鼠标左键, 击中实体是否是玩家, 以及击中的玩家是否死亡
if (button === 'action0' && isPlayer(entity)) {
// 获取双方的状态
const state = playerState[entity.player.name];
const weapon = WEAPON[state.weapon]; // 获取武器信息
if (state.shootTick + weapon.interval > tick) return; // 小于武器间隔事件不处理
state.shootTick = tick; // 更新射击时间(tick)
const fireSound = {
'sample': weapon.sound,
'radius': 32,
'gain': 1,
'pitch': 1,
'gainRange': 0,
'pitchRange': 0,
};
// 播放发射子弹音效
entity.sound(fireSound);
// 发射子弹
fireBullet(entity.position, direction);
showMuzzleFlash(entity);
if (isPlayer(hitEntity)) {
const otherState = playerState[hitEntity.player.name];
if (hitEntity.dead) return // 已死亡玩家不处理
if (state.team === otherState.team) return; // 相同队伍不处理
// 设置击中效果
hitEntity.velocity.x += direction.x;
hitEntity.velocity.z += direction.z;
// 计算伤害并应用
const basicDamage = Math.max(1, weapon.damage * Math.min(1, weapon.distance / distance));
const damage = Math.ceil(basicDamage * (1 + Math.random() * 0.3));
hitEntity.hurt(damage, { attacker: entity });
}
}
});
});
// 收到伤害的通知
world.onTakeDamage(({entity, damage, attacker}) => {
if (!entity.isPlayer) return;
const attackerName = attacker.isPlayer ? attacker.player.name : attacker.id;
entity.player.directMessage(`[剩余HP: ${entity.hp}]: 你受到了${damage}点来自@${attackerName}的伤害`);
});
// 获取武器
world.querySelectorAll('.武器').forEach((weapon)=>{
weapon.enableInteract = true;
weapon.interactRadius = 5;
weapon.onInteract(({ entity }) => {
if (isPlayer(entity) && weapon.id in WEAPON) {
const state = playerState[entity.player.name]; // 获取玩家状态
state.weapon = weapon.id; // 设置武器
entity.player.directMessage(`你获得了${weapon.id}`); // 通知玩家
// 更新装备
removeSuit(entity);
wearSuit(entity, state.team, weapon.id);
}
})
})
// 忽略玩家与子弹碰撞
world.addCollisionFilter(`.bullet`, `player`);
// 清理与实体碰撞的子弹
world.onEntityContact(({entity, other})=>{
if (entity.hasTag('bullet')) entity.destroy();
if (other.hasTag('bullet')) other.destroy();
})
// 清理碰到方块的子弹
world.onVoxelContact(({entity})=>{
if (entity.hasTag('bullet')) entity.destroy();
})
// 清理超出地图空间的子弹
const area = world.addZone({
selector: '.bullet',
bounds: {
lo: [0, 0, 0],
hi: [128, 64, 128],
},
});
area.onLeave(({ entity }) => {
if (entity.hasTag('bullet')) {
entity.destroy();
}
});
}
// 随机打乱数组
function arrayShuffle (array) {
for (let i = 1; i < array.length; ++i) {
const temp = array[i];
const x = (Math.random() * (i + 1)) | 0;
array[i] = array[x];
array[x] = temp;
}
return array;
}
// 为对象实现类似数组的map功能
function objectMap(obj, func) {
const o = {};
for (const key in obj) {
o[key] = func(obj[key], key);
}
return o;
}