This commit is contained in:
wfz
2026-05-02 21:30:28 +08:00
parent 9fd572c8c4
commit 18ac2f70f5
18 changed files with 772 additions and 133 deletions

View File

@@ -34,6 +34,8 @@ export class GameEngine {
this.zombieBullets = new Map()
// 掉落物列表
this.loots = new Map()
// 机枪塔列表
this.turrets = new Map()
// 游戏运行状态
this.running = false
@@ -55,9 +57,9 @@ export class GameEngine {
[WEAPONS.MACHINE_GUN]: 100,
[WEAPONS.SHOTGUN]: 20,
[WEAPONS.GRENADE]: 10,
[WEAPONS.MOLOTOV]: 5,
[WEAPONS.NUT_WALL]: 3,
[WEAPONS.AUTO_TURRET]: 2
[WEAPONS.MOLOTOV]: 10,
[WEAPONS.NUT_WALL]: 10,
[WEAPONS.AUTO_TURRET]: 5
}
// 手雷蓄力相关
@@ -396,7 +398,7 @@ export class GameEngine {
zombie.y = zs.y
zombie.health = zs.health
const angle = zs.angle || 0
this.scene.updateZombie(zs.id, zs.x, zs.y, angle, zs.health)
this.scene.updateZombie(zs.id, zs.x, zs.y, angle, zs.health, zs.isAttacking || false)
// 受伤特效
if (prevHealth > zs.health && zs.health > 0) {
this.scene.addHitEffect(zs.x, zs.y)
@@ -465,13 +467,46 @@ export class GameEngine {
// 爆炸效果
if (state.explosions) {
console.log('Explosions received:', state.explosions)
for (const exp of state.explosions) {
console.log('Creating explosion at:', exp.x, exp.y, 'radius:', exp.radius)
this.scene.addExplosion(exp.x, exp.y, exp.radius || 3)
}
}
// 燃烧区域
if (state.fireZones) {
const serverIds = new Set(state.fireZones.map(f => f.id))
// 新增或更新
for (const fz of state.fireZones) {
this.scene.addFireZone(fz.id, fz.x, fz.y, fz.radius || 2, fz.duration || 5, fz.elapsed || 0)
}
// 移除已消失的
for (const [id] of this.scene.fireZones) {
if (!serverIds.has(id)) {
this.scene.removeFireZone(id)
}
}
}
// 机枪塔
if (state.turrets) {
const serverIds = new Set(state.turrets.map(t => t.id))
for (const ts of state.turrets) {
if (!this.turrets.has(ts.id)) {
this.turrets.set(ts.id, ts)
this.scene.addTurret(ts.id, ts.x, ts.y, ts.health, ts.maxHealth, ts.isAutoTurret)
} else {
this.turrets.set(ts.id, ts)
this.scene.updateTurret(ts.id, ts.health)
}
}
for (const [id] of this.turrets) {
if (!serverIds.has(id)) {
this.turrets.delete(id)
this.scene.removeTurret(id)
}
}
}
// 命中效果
if (state.hits) {
for (const hit of state.hits) {

View File

@@ -31,6 +31,8 @@ export class GameScene {
this.bullets = [] // 玩家子弹数组
this.zombieBullets = [] // 僵尸子弹数组
this.loots = new Map() // Map<lootId, {mesh, type}>
this.fireZones = new Map() // Map<fireZoneId, {mesh, light, startTime, duration}
this.turrets = new Map() // Map<turretId, {group, healthBar, healthBarBg, isAutoTurret}
this.effects = [] // 特效数组
this.wallMeshes = [] // 墙壁网格
this.nutWalls = new Map() // Map<key, {mesh, healthBar}>
@@ -413,18 +415,24 @@ export class GameScene {
head.castShadow = true
group.add(head)
// 手臂
const armGeo = new THREE.BoxGeometry(0.12, 0.6, 0.12)
// 手臂(比身体宽,确保从各个角度都可见)
const armGeo = new THREE.BoxGeometry(0.18, 0.7, 0.18)
const armMat = new THREE.MeshLambertMaterial({ color: armColor })
const leftArm = new THREE.Mesh(armGeo, armMat)
leftArm.position.set(-0.35, 0.5, 0.2)
leftArm.position.set(-0.5, 0.5, 0.0)
leftArm.rotation.x = -0.5
leftArm.castShadow = true
group.add(leftArm)
const rightArm = new THREE.Mesh(armGeo, armMat)
rightArm.position.set(0.35, 0.5, 0.2)
rightArm.position.set(0.5, 0.5, 0.0)
rightArm.rotation.x = -0.5
rightArm.castShadow = true
group.add(rightArm)
// 存储手臂引用供攻击动画使用
group.userData.leftArm = leftArm
group.userData.rightArm = rightArm
// 精英僵尸发光效果
if (isElite) {
const glowGeo = new THREE.SphereGeometry(0.6, 8, 8)
@@ -514,7 +522,12 @@ export class GameScene {
const model = this.createZombieModel(isElite, isSplitter)
model.position.set(x, 0, y)
this.scene.add(model)
this.zombies.set(id, { model, isElite, isSplitter })
this.zombies.set(id, {
model, isElite, isSplitter,
leftArm: model.userData.leftArm,
rightArm: model.userData.rightArm,
isAttacking: false
})
}
/**
@@ -534,19 +547,34 @@ export class GameScene {
}
/**
* 更新僵尸位置角度
* 更新僵尸位置角度和攻击动画
* @param {string} id 僵尸ID
* @param {number} x X坐标
* @param {number} y Y坐标
* @param {number} angle 旋转角度
* @param {number} health 生命值
* @param {boolean} isAttacking 是否正在攻击
*/
updateZombie(id, x, y, angle, health) {
updateZombie(id, x, y, angle, health, isAttacking = false) {
const zombie = this.zombies.get(id)
if (zombie) {
zombie.model.position.x = x
zombie.model.position.z = y
zombie.model.rotation.y = angle
// 攻击动画:手臂前后挥动
if (zombie.leftArm && zombie.rightArm) {
if (isAttacking) {
const t = Date.now() * 0.012
const swing = Math.sin(t) * 1.0
zombie.leftArm.rotation.x = -1.5 + swing
zombie.rightArm.rotation.x = -1.5 - swing
} else {
zombie.leftArm.rotation.x = -0.5
zombie.rightArm.rotation.x = -0.5
}
}
zombie.isAttacking = isAttacking
}
}
@@ -864,6 +892,164 @@ export class GameScene {
}
}
/**
* 添加火焰区域效果
* @param {number} id 火焰区域ID
* @param {number} x X坐标
* @param {number} y Y坐标
* @param {number} radius 火焰半径
* @param {number} duration 持续时间(秒)
* @param {number} elapsed 已经过时间(秒)
*/
addFireZone(id, x, y, radius, duration, elapsed) {
if (this.fireZones.has(id)) return
// 火焰地面圆
const geo = new THREE.CircleGeometry(radius, 24)
const mat = new THREE.MeshBasicMaterial({
color: 0xff4400,
transparent: true,
opacity: 0.5,
side: THREE.DoubleSide
})
const mesh = new THREE.Mesh(geo, mat)
mesh.rotation.x = -Math.PI / 2
mesh.position.set(x, 0.05, y)
this.scene.add(mesh)
// 火焰光源
const light = new THREE.PointLight(0xff6600, 2, radius * 3)
light.position.set(x, 1, y)
this.scene.add(light)
this.fireZones.set(id, { mesh, light, startTime: Date.now() - elapsed * 1000, duration: duration * 1000 })
}
/**
* 移除火焰区域效果
* @param {number} id 火焰区域ID
*/
removeFireZone(id) {
const fz = this.fireZones.get(id)
if (!fz) return
this.scene.remove(fz.mesh)
this.scene.remove(fz.light)
fz.mesh.geometry.dispose()
fz.mesh.material.dispose()
this.fireZones.delete(id)
}
/**
* 添加机枪塔渲染
* @param {number} id 实体ID
* @param {number} x X坐标
* @param {number} y Y坐标
* @param {number} health 当前生命值
* @param {number} maxHealth 最大生命值
* @param {boolean} isAutoTurret 是否为自动机枪塔false则为坚果墙塔基
*/
addTurret(id, x, y, health, maxHealth, isAutoTurret) {
if (this.turrets.has(id)) return
const group = new THREE.Group()
// 塔基(底座)
const baseGeo = new THREE.CylinderGeometry(0.35, 0.4, 0.3, 8)
const baseMat = new THREE.MeshLambertMaterial({ color: 0x555555 })
const base = new THREE.Mesh(baseGeo, baseMat)
base.position.y = 0.15
base.castShadow = true
group.add(base)
if (isAutoTurret) {
// 机身
const bodyGeo = new THREE.CylinderGeometry(0.2, 0.25, 0.4, 8)
const bodyMat = new THREE.MeshLambertMaterial({ color: 0x336633 })
const body = new THREE.Mesh(bodyGeo, bodyMat)
body.position.y = 0.5
body.castShadow = true
group.add(body)
// 枪管
const barrelGeo = new THREE.CylinderGeometry(0.05, 0.05, 0.5, 6)
const barrelMat = new THREE.MeshLambertMaterial({ color: 0x222222 })
const barrel = new THREE.Mesh(barrelGeo, barrelMat)
barrel.rotation.x = Math.PI / 2
barrel.position.set(0, 0.55, 0.25)
barrel.castShadow = true
group.add(barrel)
// 炮塔顶部
const topGeo = new THREE.SphereGeometry(0.15, 8, 6)
const topMat = new THREE.MeshLambertMaterial({ color: 0x446644 })
const top = new THREE.Mesh(topGeo, topMat)
top.position.y = 0.75
group.add(top)
} else {
// 坚果墙塔基 - 木箱外观
const boxGeo = new THREE.BoxGeometry(0.7, 0.8, 0.7)
const boxMat = new THREE.MeshLambertMaterial({ color: 0x8B4513 })
const box = new THREE.Mesh(boxGeo, boxMat)
box.position.y = 0.7
box.castShadow = true
group.add(box)
}
// 血条背景
const barBgGeo = new THREE.PlaneGeometry(0.8, 0.08)
const barBgMat = new THREE.MeshBasicMaterial({ color: 0x333333, side: THREE.DoubleSide })
const barBg = new THREE.Mesh(barBgGeo, barBgMat)
barBg.position.set(0, 1.3, 0)
barBg.rotation.x = -0.3
group.add(barBg)
// 血条前景
const barGeo = new THREE.PlaneGeometry(0.78, 0.06)
const barMat = new THREE.MeshBasicMaterial({ color: 0x00ff00, side: THREE.DoubleSide })
const healthBar = new THREE.Mesh(barGeo, barMat)
healthBar.position.set(0, 1.3, 0.01)
healthBar.rotation.x = -0.3
group.add(healthBar)
group.position.set(x, 0, y)
this.scene.add(group)
this.turrets.set(id, { group, healthBar, healthBarBg: barBg, isAutoTurret, maxHealth })
}
/**
* 更新机枪塔血量显示
*/
updateTurret(id, health) {
const turret = this.turrets.get(id)
if (!turret) return
const pct = Math.max(0, health / turret.maxHealth)
turret.healthBar.scale.x = pct
turret.healthBar.position.x = -(1 - pct) * 0.39
if (pct > 0.5) {
turret.healthBar.material.color.setHex(0x00ff00)
} else if (pct > 0.25) {
turret.healthBar.material.color.setHex(0xffff00)
} else {
turret.healthBar.material.color.setHex(0xff0000)
}
}
/**
* 移除机枪塔
*/
removeTurret(id) {
const turret = this.turrets.get(id)
if (!turret) return
this.scene.remove(turret.group)
turret.group.traverse(child => {
if (child.geometry) child.geometry.dispose()
if (child.material) child.material.dispose()
})
this.turrets.delete(id)
}
/**
* 添加枪口火焰效果
* @param {number} x X坐标
@@ -1084,6 +1270,19 @@ export class GameScene {
}
}
// 更新火焰区域闪烁效果
for (const [id, fz] of this.fireZones) {
const elapsed = (now - fz.startTime) / 1000
if (elapsed * 1000 >= fz.duration) {
this.removeFireZone(id)
} else {
// 火焰闪烁
const flicker = 0.3 + Math.sin(now * 0.01 + id) * 0.15 + Math.sin(now * 0.023) * 0.1
fz.mesh.material.opacity = flicker
fz.light.intensity = 1.5 + Math.sin(now * 0.015) * 0.5
}
}
// 更新子弹拖尾
for (const bullet of this.bullets) {
if (!bullet.isGrenade && !bullet.isMolotov && bullet.trail) {

View File

@@ -94,8 +94,8 @@ export const WEAPON_CONFIG = {
name: 'Molotov',
damage: 80,
fireRate: 2000,
ammo: 5,
maxAmmo: 5,
ammo: 10,
maxAmmo: 10,
speed: 12,
spread: 0,
pellets: 1,
@@ -110,8 +110,8 @@ export const WEAPON_CONFIG = {
name: 'Wall',
damage: 0,
fireRate: 1000,
ammo: 3,
maxAmmo: 3,
ammo: 10,
maxAmmo: 10,
speed: 0,
spread: 0,
pellets: 0,
@@ -124,8 +124,8 @@ export const WEAPON_CONFIG = {
name: 'Turret',
damage: 0,
fireRate: 2000,
ammo: 2,
maxAmmo: 2,
ammo: 5,
maxAmmo: 5,
speed: 0,
spread: 0,
pellets: 0,