Phaser 3子弹功能异常求助:点击触发游戏崩溃
问题分析与修复方案
核心错误点
- 子弹无物理体:用
game.add.image()创建子弹,该方法生成的对象默认不带物理组件,导致bul.body未定义,触发点击崩溃。 - 提前启用未初始化对象的物理体:
create()中调用game.physics.arcade.enable(bul)时,bul还未创建(值为undefined),属于无效操作。 - 点击坐标错误:使用
game.input.x/y获取的是屏幕像素坐标,而非游戏世界坐标,若游戏有缩放或相机移动会导致方向计算偏差。 - 未初始化对象属性访问:
enemy_move()中直接访问de3.body,但de3仅在enemy_spawn3()中创建,初始阶段未执行该函数,会触发初始化报错。
修复后的完整代码
'use strict' const game = new Phaser.Game(1675, 800, Phaser.AUTO, 'game-canvas', { preload, create, update }) let tma let hp let dude let fon let cursor let bot let de1 let de2 let de3 let counter = 0 let epc = 0 // 用数组管理子弹,避免单变量覆盖旧子弹 let bullets = [] function preload() { game.load.image("de1", "de1.png") game.load.image("de2", "de2.png") game.load.image("de3", "de3.png") game.load.image("ba", "ba.png") game.load.image("ts", "16x16_tileset_platfprms_sunset(1).png") game.load.image("bul", "bullet.png") game.load.tilemap("tm", 'tm1.json', null, Phaser.Tilemap.TILED_JSON) game.load.spritesheet('dude', 'dude-green.288x40.9x1.png', 288 / 9, 40 / 1) // 移除重复的子弹资源加载 } function create() { crmap() dudee() enemy_spawn() // 移除无效的物理体启用代码 } function update() { game.physics.arcade.collide(dude, bot) dude_move() counter++ if (game.input.activePointer.isDown && counter >= 100) { bullet() counter = 0 } // 自动销毁超出游戏边界的子弹,避免内存泄漏 bullets.forEach((bullet, index) => { if (!bullet.world.bounds.contains(bullet.x, bullet.y)) { bullet.destroy() bullets.splice(index, 1) } }) } function crmap(params) { tma = game.add.tilemap('tm') tma.addTilesetImage("tileset", 'ts') bot = tma.createLayer("bot") tma.setCollisionBetween(1, 999, true) } function dudee() { dude = game.add.sprite(game.width / 2, game.height / 2, "dude") dude.scale.setTo(2) dude.frame = 4 dude.animations.add('left', [0, 1, 2, 3], 10, false) dude.animations.add("right", [5, 6, 7, 8], 10, false) cursor = game.input.keyboard.createCursorKeys() game.physics.arcade.enable(dude) dude.body.gravity.y = 400 dude.body.collideWorldBounds = true game.physics.arcade.collide(dude, bot) } function dude_move() { if (cursor.right.isDown) { dude.body.velocity.x = 200 dude.animations.play("right") } else if (cursor.left.isDown) { dude.body.velocity.x = -200 dude.animations.play("left") } else { dude.body.velocity.x = 0 dude.frame = 4 } if (cursor.up.justDown) { dude.body.velocity.y = -200 } } function enemy_spawn() { de1 = game.add.sprite(game.rnd.integerInRange(0, 1675 - 94.5), 0, 'de1') de1.scale.setTo(0.3) game.physics.arcade.enable(de1) de1.body.collideWorldBounds = true game.physics.arcade.collide(de1, bot) // 敌人移动逻辑移到生成函数内,避免访问未初始化对象 de1.body.velocity.setTo(game.rnd.integerInRange(100, 200), 0) de1.body.bounce.setTo(1) } function enemy_spawn2() { de2 = game.add.sprite(game.rnd.integerInRange(0, 1675 - 94.5), 0, 'de2') de2.scale.setTo(0.3) game.physics.arcade.enable(de2) de2.body.collideWorldBounds = true game.physics.arcade.collide(de2, bot) de2.body.velocity.setTo(game.rnd.integerInRange(150, 250), 0) de2.body.bounce.setTo(1) } function enemy_spawn3() { de3 = game.add.sprite(game.rnd.integerInRange(0, 1675 - 94.5), 0, 'de3') de3.scale.setTo(0.3) game.physics.arcade.enable(de3) de3.body.collideWorldBounds = true game.physics.arcade.collide(de3, bot) de3.body.velocity.setTo(game.rnd.integerInRange(400, 500), 0) de3.body.bounce.setTo(1) } function enemy_poof1() { de1.destroy() epc += 1 enemy_spawn2() } function enemy_poof2() { de2.destroy() epc += 1 enemy_spawn3() } function enemy_poof3() { de3.destroy() epc += 1 } function bullet() { // 用sprite创建子弹,默认支持物理体 let bul = game.add.sprite(dude.x, dude.y, "bul") bullets.push(bul) game.physics.arcade.enable(bul) const playerX = dude.x; const playerY = dude.y; // 使用游戏世界坐标,而非屏幕坐标 const inputX = game.input.activePointer.worldX; const inputY = game.input.activePointer.worldY; // 计算方向向量并归一化 const directionX = inputX - playerX; const directionY = inputY - playerY; const length = Math.sqrt(directionX * directionX + directionY * directionY); // 防止点击玩家自身位置时除以0 if (length === 0) return; const normalizedDirectionX = directionX / length; const normalizedDirectionY = directionY / length; // 设置子弹速度,调高数值更符合游戏体验 const bulletSpeed = 500; bul.body.velocity.setTo(normalizedDirectionX * bulletSpeed, normalizedDirectionY * bulletSpeed); } function bullet_destroy(bullet) { // 传入具体子弹对象销毁,避免依赖全局变量 bullet.destroy() const index = bullets.indexOf(bullet) if (index !== -1) bullets.splice(index, 1) }
额外优化说明
- 改用数组
bullets管理所有子弹,避免全局变量被新子弹覆盖,同时方便批量处理(如自动销毁、碰撞检测) - 简化
dude_move中的静止状态判断逻辑,移除冗余的cursor创建操作 - 将敌人移动逻辑拆分到对应生成函数中,避免访问未初始化对象
- 添加子弹自动销毁逻辑,防止内存泄漏
内容的提问来源于stack exchange,提问作者wemod
相关产品推荐
相关产品推荐

