Phaser从第二场景切回第一场景时玩家冻结问题求助
修复Phaser场景切换后玩家角色冻结问题
问题根源
- 全局
player、cursors变量未做场景隔离,切换后新场景逻辑可能引用旧场景对象,或同一变量被多次赋值引发冲突。 - Socket事件监听未在场景销毁时清理,每次进入场景都会重复绑定,同一事件触发多次回调干扰玩家状态更新。
- 场景切换时未传递玩家当前位置,且旧场景物理对象未清理,导致物理世界状态混乱。
修复方案
以下是修改后的完整代码,解决了上述所有问题:
class FirstScene extends Phaser.Scene { constructor() { super({ key: 'first' }); this.player = null; this.cursors = null; this.otherPlayers = {}; this.mice = {}; } init(data) { // 接收从上一场景传递的玩家位置 this.playerSpawnX = data?.x || 400; this.playerSpawnY = data?.y || 500; } preload() { this.load.image('player', 'assets/player.png'); this.load.image('otherPlayer', 'assets/player.png'); this.load.image('mouse', 'assets/mouse.png'); } create() { this.cursors = this.input.keyboard.createCursorKeys(); let self = this; // 绑定Socket事件并保存回调引用,方便后续清理 this.currentPlayersCallback = players => { Object.keys(players).forEach(playerId => { if (playerId === socket.id) { this.addPlayer(players[playerId]); } else { this.addOtherPlayers(players[playerId]); } }); }; socket.on('currentPlayers', this.currentPlayersCallback); this.newPlayerCallback = playerInfo => { this.addOtherPlayers(playerInfo); }; socket.on('newPlayer', this.newPlayerCallback); this.disconnectPlayerCallback = playerId => { if (this.otherPlayers[playerId]) { this.otherPlayers[playerId].destroy(); delete this.otherPlayers[playerId]; } }; socket.on('disconnectPlayer', this.disconnectPlayerCallback); this.playerMovedCallback = playerInfo => { if (this.otherPlayers[playerInfo.playerId]) { this.otherPlayers[playerInfo.playerId].x = playerInfo.x; this.otherPlayers[playerInfo.playerId].y = playerInfo.y; } }; socket.on('playerMoved', this.playerMovedCallback); this.mouseSpawnedCallback = mouseInfo => { const mouse = this.add.sprite(mouseInfo.x, mouseInfo.y, 'mouse').setDisplaySize(50, 50); mouse.id = mouseInfo.id; this.mice[mouse.id] = mouse; }; socket.on('mouseSpawned', this.mouseSpawnedCallback); this.addPlayer({ x: this.playerSpawnX, y: this.playerSpawnY }); const trigger = this.add.rectangle(400, -12, 100, 100, 0xffffff).setDisplaySize(50, 50); this.physics.add.existing(trigger); trigger.body.setImmovable(true); this.physics.add.collider(this.player, trigger, () => { // 切换场景时传递当前玩家位置 this.scene.switch('second', { x: this.player.x, y: this.player.y }); }); } update() { if(this.player) { if (this.cursors.left.isDown) { this.player.setVelocityX(-160); } else if (this.cursors.right.isDown) { this.player.setVelocityX(160); } else { this.player.setVelocityX(0); } if (this.cursors.up.isDown) { this.player.setVelocityY(-160); } else if (this.cursors.down.isDown) { this.player.setVelocityY(160); } else { this.player.setVelocityY(0); } socket.emit('playerMovement', { x: this.player.x, y: this.player.y }); } } shutdown() { // 场景销毁时清理Socket事件监听 socket.off('currentPlayers', this.currentPlayersCallback); socket.off('newPlayer', this.newPlayerCallback); socket.off('disconnectPlayer', this.disconnectPlayerCallback); socket.off('playerMoved', this.playerMovedCallback); socket.off('mouseSpawned', this.mouseSpawnedCallback); // 清理当前场景的其他对象 Object.values(this.otherPlayers).forEach(player => player.destroy()); Object.values(this.mice).forEach(mouse => mouse.destroy()); this.otherPlayers = {}; this.mice = {}; } addPlayer(playerInfo) { this.player = this.physics.add.image(playerInfo.x, playerInfo.y, 'player').setDisplaySize(200, 200); this.player.setCollideWorldBounds(true); } addOtherPlayers(playerInfo) { const otherPlayer = this.add.sprite(playerInfo.x, playerInfo.y, 'otherPlayer').setDisplaySize(200, 200); otherPlayer.playerId = playerInfo.playerId; this.otherPlayers[playerInfo.playerId] = otherPlayer; } } class SecondScene extends Phaser.Scene { constructor() { super({ key: 'second' }); this.player = null; this.cursors = null; this.otherPlayers = {}; this.mice = {}; } init(data) { this.playerSpawnX = data?.x || 400; this.playerSpawnY = data?.y || 500; } preload() { this.load.image('player', 'assets/player.png'); this.load.image('otherPlayer', 'assets/player.png'); this.load.image('mouse', 'assets/mouse.png'); this.load.image('background', 'assets/background.png'); } create() { this.cursors = this.input.keyboard.createCursorKeys(); this.add.sprite(0, 0, 'background').setOrigin(0, 0); let self = this; this.currentPlayersCallback = players => { Object.keys(players).forEach(playerId => { if (playerId === socket.id) { this.addPlayer(players[playerId]); } else { this.addOtherPlayers(players[playerId]); } }); }; socket.on('currentPlayers', this.currentPlayersCallback); this.newPlayerCallback = playerInfo => { this.addOtherPlayers(playerInfo); }; socket.on('newPlayer', this.newPlayerCallback); this.disconnectPlayerCallback = playerId => { if (this.otherPlayers[playerId]) { this.otherPlayers[playerId].destroy(); delete this.otherPlayers[playerId]; } }; socket.on('disconnectPlayer', this.disconnectPlayerCallback); this.playerMovedCallback = playerInfo => { if (this.otherPlayers[playerInfo.playerId]) { this.otherPlayers[playerInfo.playerId].x = playerInfo.x; this.otherPlayers[playerInfo.playerId].y = playerInfo.y; } }; socket.on('playerMoved', this.playerMovedCallback); this.mouseSpawnedCallback = mouseInfo => { const mouse = this.add.sprite(mouseInfo.x, mouseInfo.y, 'mouse').setDisplaySize(50, 50); mouse.id = mouseInfo.id; this.mice[mouse.id] = mouse; }; socket.on('mouseSpawned', this.mouseSpawnedCallback); this.addPlayer({ x: this.playerSpawnX, y: this.playerSpawnY }); const trigger = this.add.rectangle(400, -12, 100, 100, 0xffffff).setDisplaySize(50, 50); this.physics.add.existing(trigger); trigger.body.setImmovable(true); this.physics.add.collider(this.player, trigger, () => { this.scene.switch('first', { x: this.player.x, y: this.player.y }); }); } update() { if(this.player) { if (this.cursors.left.isDown) { this.player.setVelocityX(-160); } else if (this.cursors.right.isDown) { this.player.setVelocityX(160); } else { this.player.setVelocityX(0); } if (this.cursors.up.isDown) { this.player.setVelocityY(-160); } else if (this.cursors.down.isDown) { this.player.setVelocityY(160); } else { this.player.setVelocityY(0); } socket.emit('playerMovement', { x: this.player.x, y: this.player.y }); } } shutdown() { socket.off('currentPlayers', this.currentPlayersCallback); socket.off('newPlayer', this.newPlayerCallback); socket.off('disconnectPlayer', this.disconnectPlayerCallback); socket.off('playerMoved', this.playerMovedCallback); socket.off('mouseSpawned', this.mouseSpawnedCallback); Object.values(this.otherPlayers).forEach(player => player.destroy()); Object.values(this.mice).forEach(mouse => mouse.destroy()); this.otherPlayers = {}; this.mice = {}; } addPlayer(playerInfo) { this.player = this.physics.add.image(playerInfo.x, playerInfo.y, 'player').setDisplaySize(200, 200); this.player.setCollideWorldBounds(true); } addOtherPlayers(playerInfo) { const otherPlayer = this.add.sprite(playerInfo.x, playerInfo.y, 'otherPlayer').setDisplaySize(200, 200); otherPlayer.playerId = playerInfo.playerId; this.otherPlayers[playerInfo.playerId] = otherPlayer; } } const config = { type: Phaser.AUTO, width: 800, height: 600, physics: { default: 'arcade', arcade: { gravity: { y: 0 } } }, scene: [FirstScene, SecondScene] }; const game = new Phaser.Game(config); const socket = io();
关键修改说明
- 将
player、cursors等变量从全局改为场景实例变量,确保每个场景独立维护自身对象,避免跨场景引用冲突。 - 在场景
shutdown生命周期中清理Socket事件监听,防止重复绑定导致的逻辑混乱。 - 通过
init方法接收场景切换时传递的玩家位置,保证切换后玩家位置连续,不会重置到初始点。 - 场景销毁时手动清理其他玩家和老鼠对象,避免内存泄漏和渲染异常。
内容的提问来源于stack exchange,提问作者Kory
相关产品推荐
相关产品推荐

