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

Phaser多场景切换时setCollisionByProperty报n未定义错误解决

Phaser多场景切换调用setCollisionByProperty报错修复

问题背景

我是Phaser初学者,正在开发一款迷你RPG游戏。已完成地图制作并导出为.json格式文件,使用setCollisionByProperty方法实现精灵与瓦片的碰撞检测。
单场景测试时碰撞功能运行正常,但启用多场景模式、切换返回场景时会抛出如下错误:

Uncaught TypeError: n is undefined

经多轮测试排查,问题与setCollisionByProperty方法的调用相关,相关代码如下:

游戏入口配置代码

import GameScene from "./GameScene.js"
import maisonScene from "./maisonScene.js"
import Joueur from "./joueur.js"

/*  * Configuration du jeu  */
var config = { 
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    physics :{
        default : 'arcade',
        arcade:{
            debug : true, //Pour debug les collisions
            gravity :{
                y:0
            },
        }
    },
    scene: {
        preload: preload,
        create: create,
        update: update
    },
    pixelArt: true,
    scene : [maisonScene, GameScene], 
};

var game = new Phaser.Game(config); 
export let joueur = new Joueur();

function preload(){}
function create(){}
function update(){}

场景逻辑代码

GameScene与maisonScene逻辑完全一致,代码如下:

let staticLayer =[] //tableau contenant les calques du paysage 

export default class GameScene extends Phaser.Scene{
    constructor(){
        super({key : "GameScene"})
    }

    create(){
        //chargement de la carte
        const carteDuNiveau = this.add.tilemap("carte");

        //chargement des tuiles
        const tileset = carteDuNiveau.addTilesetImage("Citique","tuiles");

        //chargement du calque sol
        staticLayer.push(carteDuNiveau.createLayer("sol",tileset));   

        //chargement du calque chemin
        staticLayer.push(carteDuNiveau.createLayer("chemin",tileset));

        //chargement du calque fleurs
        staticLayer.push(carteDuNiveau.createLayer("fleurs",tileset));

        player = this.physics.add.sprite(300, 150, 'player');
        player.body.setSize(40,22,false) //Redimensionner la hitbox du joueur
        player.body.setOffset(4,42) //Deplacer la hitbox vers le bas du joueur
        player.setScale(0.6);
        
        staticLayer.forEach((element)=>{
            element.setScale(2);
            element.setCollisionByProperty({ estSolide: true });
            // ajout d'une collision entre le joueur et les calques   
            this.physics.add.collider(player, element);
        })
    }
}

代码中staticLayer数组用于存储所有地图图层。


问题根因

报错核心原因是staticLayer数组定义在Scene类外部,属于模块级全局变量:

  • 每次进入场景执行create方法时,都会往数组里push新创建的图层对象
  • 场景切换离开时,Phaser会自动销毁当前场景的所有游戏对象(包括之前创建的图层),但这些已销毁的失效图层引用仍然残留在全局staticLayer数组中
  • 再次进入场景遍历staticLayer数组时,会拿到已经被销毁的无效对象,调用setCollisionByProperty方法时就会触发n is undefined的类型错误

另外入口配置里重复定义了scene属性,后面的数组配置会覆盖前面写的preload/create/update空函数配置,属于潜在问题。


修复方案

  • 把staticLayer数组改为场景实例的内部属性,不要放在类外部做全局变量,避免不同场景、不同生命周期的图层对象互相污染。maisonScene按照相同逻辑修改即可,两个场景各自持有独立的图层数组,互不干扰。修改后的场景代码结构如下:
export default class GameScene extends Phaser.Scene{
    constructor(){
        super({key : "GameScene"})
        // 把图层数组定义为场景自身的属性,每个场景实例独立持有
        this.staticLayer = []
    }

    create(){
        const carteDuNiveau = this.add.tilemap("carte");
        const tileset = carteDuNiveau.addTilesetImage("Citique","tuiles");

        // 所有push操作都指向当前场景自己的图层数组
        this.staticLayer.push(carteDuNiveau.createLayer("sol",tileset));   
        this.staticLayer.push(carteDuNiveau.createLayer("chemin",tileset));
        this.staticLayer.push(carteDuNiveau.createLayer("fleurs",tileset));

        // 注意player如果是全局复用的,切换场景时要把player从旧场景物理世界移除,加到当前场景
        player = this.physics.add.sprite(300, 150, 'player');
        player.body.setSize(40,22,false)
        player.body.setOffset(4,42)
        player.setScale(0.6);
        
        // 遍历当前场景自己的图层数组
        this.staticLayer.forEach((element)=>{
            element.setScale(2);
            element.setCollisionByProperty({ estSolide: true });
            this.physics.add.collider(player, element);
        })
    }

    // 场景关闭时清空数组,彻底避免残留引用
    shutdown(){
        this.staticLayer.length = 0
    }
}
  • 清理入口配置里重复的scene属性,删掉无效的空preload/create/update配置,直接保留场景数组配置即可:
var config = { 
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    physics :{
        default : 'arcade',
        arcade:{
            debug : true,
            gravity :{ y:0 },
        }
    },
    pixelArt: true,
    scene : [maisonScene, GameScene], 
};
  • 如果你需要跨场景复用player精灵,记得在场景切换时不要重复创建player,而是把已有的player实例通过this.add.existing(player)、this.physics.add.existing(player)的方式加入当前场景,避免重复创建精灵引发的额外碰撞问题。

内容的提问来源于stack exchange,提问作者Gwyn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 19:27:23