引擎Bug反馈一:cc.Enum(cc.macro.KEY)序列化显示报错

上来就喷垃圾,那就不要怪别人了

##今天就是如来佛祖来了,也不能说是我开发者的问题。

1.先说真正报错的原因,原因其实就是 (@andrewlu )所说的值重复而报错,他说的是正确的,也不怕被骂。
2.然后先看一下引擎内部的代码engine\cocos2d\core\platform\CCEnum.js,看一下报错的代码。
CCEnum.js的Enum方法:


function Enum (obj) {
    if ('__enums__' in obj) {
        return obj;
    }
    js.value(obj, '__enums__', null, true);

    var lastIndex = -1;
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        var val = obj[key];

        if (val === -1) {
            val = ++lastIndex;
            obj[key] = val;
        }
        else {
            if (typeof val === 'number') {
                lastIndex = val;
            }
            else if (typeof val === 'string' && Number.isInteger(parseFloat(key))) {
                continue;
            }
        }
        var reverseKey = '' + val;
        if (key !== reverseKey) {
            if ((CC_EDITOR || CC_TEST) && reverseKey in obj && obj[reverseKey] !== key) {
                cc.errorID(7100, reverseKey);
                continue;
            }
            js.value(obj, reverseKey, key);
        }
    }
    return obj;
}

obj[reverseKey] !== key 
//这个地方使用value当作key,判断了与最后一个key是否与当前value的key作比较,
//如果不相等,就表示有value是重复的。

3.也就是说我这个写法是可以正常运行的,既然能正常运行引擎却报错,你说是我开发者的问题,我他妈的打死也不认,如果是能正常运行的代码,你顶多只能发出警告而不是直接出红报错。
4.为什么(哲锋 @zzf_2025)的写法可以通过编译,他说的是引用问题,其实并不是,只是他的写法正好绕过了cc.Enum()方法的检查:

//他是这样写的
var KEY = cc.Enum({});
Object.assign(KEY, cc.macro.KEY);
//因为他先构成了一个空的枚举对象,所以正好绕过的cc.Enum()方法里的for循环
var keys = Object.keys(obj);
 for (var i = 0; i < keys.length; i++) {}

所以说他这并不是真正的找到问题所在。虽然说这个方法可以通过编译,但你们做引擎也不能让我们开发者为了让你们引擎本身不报错而去用另外一种写法做兼容。你们觉得这么做是合理的吗?
4. @jare 你跟我说:“字典,不能用于枚举的定义”。ts脚本里的enum转换成js脚本之后,它们不都是js的Object吗(看测试代码)。

const {ccclass, property} = cc._decorator;

var LanguageA={CN:0,EN:1}
enum LanguageB{CN,EN}

@ccclass
export default class Test extends cc.Component {

    @property({type:cc.Enum(LanguageA),visible:true})
    private _languageA=LanguageA.CN;
    
    @property({type:cc.Enum(LanguageB),visible:true})
    private _languageB=LanguageB.CN;

    start () {
        cc.log("===Test.start();===");
        cc.log("LanguageA:",LanguageA);
        cc.log("LanguageB",LanguageB);
    }
}

log截图:

请问有什么不一样,我这里不是能正常通过编译和运行吗,字典和enum通过cc.Enum()定义出来的有什么不同吗?
你们给我们写的ts提示文档不也是enum吗:

5.你们一个个还跟我犟,你们两个都是打着“引擎开发团队”的标签,你们都没有找到真正的问题所在,你们是真的在用心对待问题吗。
6.我之前给unity提交过一个Bug,我也一样骂,也一样喷,用得不爽我就是喷,但是那个人来来回回和我发邮件跟踪了两三个月去解决 一个问题,换成你们有这个耐心吗,你们这样还想打造世界一流的引擎吗,还是先回家洗洗睡吧

有个性,666,

ts 的 enum 规范里似乎并没有 value 为 uniq 的限制,确实可以存在以下写法

enum Test {
   A = 1,
   B = 1,
}

不过引擎里的 cc.Enum 类不允许重复定义 value 的情况存在
所以类似以下的写法都是不正确的

cc.Enum({
  A: 1,
  B: 1,
});

cc.Enum( cc.macro.KEY ) 同理

1赞

可以正常运行,但是当使用同一个 value 反查 key 时,将只能查到其中一个 key。也就是说,枚举支持反查 key。

let t = cc.Enum({
  OK: 0,
  YES: 0,
  BU_OK: 1,
});
console.log(t[t.YES]); // YES
console.log(t[t.BU_OK]); // BU_OK

如果 value 重复了,反查 key 就会出问题。

console.log(t[t.OK]); // YES ???

当开发者获取到之前保存的值,例如这里的 0 的时候,无论如何只能拿到 YES。那编辑器里面就会出现不可思议的一幕,策划明明设置一个下拉列表为 OK 了,但是场景保存再打开还是会变回 YES。

所以这种情况下报错是天经地义,你用了没问题不代表别人用了也没问题。

我们本来就不支持把内建的这个 key 作为一个 CCEnum,是你非要这么用。如果真的有这种需要,那我们应该是做一个专门的控件,点一下之后允许策划按下键盘上的一个按钮,保存这个按钮的值,而不是在上百个按键里查找自己要哪一个 key。

ts 里的 enum 正是支持通过 value 反查 key 的。只不过 ts 的 enum 不为编辑器服务,不会遇到我前面说的问题,所以没有报错。但是如果直接拿来用做编辑器,一样会有我说的永远只能反查到最后一个 key 的问题。

我们对技术交流都是欢迎的,但是如果有人觉得我们做的是垃圾引擎,抱歉那只能说明他不是我们的目标用户。我们能力有限,我们服务好自己的目标用户群就行了,不需要也没办法取悦每一个开发者。 好了我回答到了编辑器相关的问题,也帮你证明了 ts 相关的问题,最后我也想问问你同样的问题:你有找到真正的问题所在?你是真的在用心对待问题吗?

蒽,老外真有耐心,下次我要是遇到报错了,我也去骂他是 ** 引擎试试哈。

本来真的懒得跟你争,老板大半夜看到了有点不爽,我来解释清楚吧。

2赞

说实话 我想知道 如果开发者有一半像楼主这样的 开源引擎方还能、还愿意继续干下去吗?:14:

不知道这个能否解决你的问题!

我在 ShaderHelper 中动态定义的 Enum,枚举文件名显示到组件面板上!
看下在面板上的效果:

let ShaderEnum = cc.Enum({});
...
export default class ShaderHelper extends cc.Component {
    //枚举Shader Effect 文件
    @property
    _program = 0;
    @property({type:ShaderEnum})
    get program() {
        return this._program;
    }
    set program(value) {
        ...
        console.log(ShaderEnum[this._program]); //打印Key
        ...
    }
   ...
}

在文件最后,运行时动态组装生成ShaderEnum

cc.game.on(cc.game.EVENT_ENGINE_INITED, () => {
    cc.loader.loadResDir('effect', cc.EffectAsset ,(error, res) => {
        ShaderHelper.effectAssets = res;
        let obj = {};
        let array = ShaderHelper.effectAssets.map((item, i)  => {
            obj[item._name] = -1;  //注意所有的 Value 都为-1
            return {name:item._name, value: i}; 
        });
        //运行时重新赋值ShaderEnum
        ShaderEnum = cc.Enum(obj);
        //@ts-ignore
        cc.Class.Attr.setClassAttr(ShaderHelper, 'program', 'enumList', array);
    });
})

再看下数据构:

Enum 的 Key\Value只要不重复就 OK 了!

开源版本仓库:
https://github.com/ShawnZhang2015/ShaderHelper2

1赞

为什么cc.macro.KEY做type可以,我自定义的Key不行,报错如下

编辑器面板显示如下

最终在浏览器运行,两个都报错了

看了下引擎的定义,cc.macro.KEY也是一个表

为什么一个可以,一个不行??type:cc.macro.KEY 能不能用?似乎编辑器跟浏览器运行时,cc.marco.KEY是不同的对象

暴躁小哥请放弃cocos出门右转,屁大点问题骂个天昏地暗,你要把引擎开发团队心态搞崩。

本来就是开源引擎白嫖,靠大家努力一起完善,出了问题不能心平气和的说?你说你喷unity,请问你用的正版还是盗版?如果是正版,你付了钱,你是顾客,unity乐得被喷。如果是盗版,那我就呵呵了。

(键盘侠警告)你这样子,假设现实项目中遇到前辈的代码出了问题,如果他在职,你敢这么当面跟他叫骂(你是什么垃圾!)吗?如果他离职,你是不是要把他八辈祖宗一起骂了?

1赞

感觉这帖可以关了,,,,可能是引擎bug多了给人狼来了的感觉吧,,,如果引擎做的足够好好评还是很多的

你的用法有问题,参考这个:

var Key = cc.Enum({
    a: 0,
    b: 1,
    c: 2
});

我知道要用cc.Enum, 而且报错的地方,也是通过cc.Enum.isEnum判定后才报错的,问题是,cc.macro.KEY我代码里面看到的也是一个单独的表,为什么使用后在编辑器里面没有报错?还可以显示出列表,当然,最终在浏览器运行时都报错了。

可能是组件状态没更新,删除组件重新挂上去试试

大哥,你是回复我吗?
还是不行,其实这样用本来就是错误的,必须要用cc.Enum封装起来,我就是好奇在编辑器里面为什么cc.macro.KEY可以,我自己定义的不行,大佬可以试试。

engineKey: {
    type: cc.macro.KEY,
    default: cc.macro.KEY.Delete
}

这样用也是会报错的。
cc.Enum(cc.macro.KEY) 能被编辑器识别为列表格式。去掉 cc.Enum 之后,由于 cc.macro.KEY 不能被赋值,所以报错之后编辑器没有刷新面板,也就导致了你看到 engineKey 能正常显示列表的假象。

引擎内部 enum 都不允许被引用赋值。必须深拷贝一个新的枚举对象出来。

多谢回复,跟你说的一样,正如@jare所说,重启编辑器后,在编辑器里面两个都报错了。