You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Phaser从第二场景切回第一场景时玩家冻结问题求助

修复Phaser场景切换后玩家角色冻结问题

问题根源

  1. 全局player、cursors变量未做场景隔离,切换后新场景逻辑可能引用旧场景对象,或同一变量被多次赋值引发冲突。
  2. Socket事件监听未在场景销毁时清理,每次进入场景都会重复绑定,同一事件触发多次回调干扰玩家状态更新。
  3. 场景切换时未传递玩家当前位置,且旧场景物理对象未清理,导致物理世界状态混乱。

修复方案

以下是修改后的完整代码,解决了上述所有问题:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 07:37:36