Cómo implementar soporte para enumeraciones de cadenas en Cocos Creator 2.4.5

Al desarrollar componentes personalizados en Cocos Creator, es posible encontrarse con limitaciones en el manejo de tipos de datos específicos. En particular, las enumeraciones basadas en cadenas no son nativamente soportadas por el panel de propiedades del editor, a diferencia de las enumeraciones numéricas. A continuación se analiza el mecanismo interno del editor y se presenta una solución técnica.

El editor admite múltiples tipos de propiedades, cada uno con su propio componente de interfza:

const TIPOS_SOPORTADOS = [
  "string", "number", "boolean", "array", "object", "enum",
  "color", "vec2", "vec3", "String", "Float", "Boolean",
  "Object", "Integer", "Enum", "asset", "cc.Asset", "cc.Node",
  "cc.Vec2", "cc.Vec3", "cc.Size", "cc.Color", "cc.Rect", "cc.Vec4"
];

Los componentes de interfaz varían según el tipo. Por ejemplo, para números:

function renderizarNumero(config) {
  const plantilla = config.slider 
    ? '<ui-slider class="flex-1"></ui-slider>'
    : '<ui-num-input class="flex-1"></ui-num-input>';
  return plantilla;
}

Para enumeraciones, se utiliza un componente selector:

function renderizarEnum() {
  return '<ui-select class="flex-1"></ui-select>';
}

La recuperación de atributos de clase se basa en el siguiente mecanismo:

function obtenerAtributosClase(constructor) {
  if (constructor.hasOwnProperty('__attrs__')) {
    return constructor.__attrs__;
  }
  return inicializarAtributos(constructor);
}

function consultarAtributo(constructor, nombrePropiedad) {
  const atributos = obtenerAtributosClase(constructor);
  const prefijo = `${nombrePropiedad}_`;
  const resultado = {};
  
  Object.keys(atributos).forEach(clave => {
    if (clave.startsWith(prefijo)) {
      resultado[clave.substring(prefijo.length)] = atributos[clave];
    }
  });
  
  return resultado;
}

La generación de listas para enumeraciones tiene una limitación crítica: solo procesa valores enteros:

function generarListaEnum(definicionEnum) {
  if (definicionEnum.__listaEnum__) {
    return definicionEnum.__listaEnum__;
  }
  
  const lista = [];
  const entradas = Object.entries(definicionEnum);
  
  entradas.forEach(([nombre, valor]) => {
    if (Number.isInteger(valor)) {
      lista.push({ nombre, valor });
    }
  });
  
  lista.sort((a, b) => a.valor - b.valor);
  return definicionEnum.__listaEnum__ = lista;
}

Este filtrado por Number.isInteger() excluye las enumeraciones de cadenas. Al inspeccionar los datos del editor, se observa que para enumeraciones de cadenas el campo tipoEnum permanece vacío, causadno errores de validación.

El flujo de datos del editor revela otro problema: durante la serialización mediante Editor.obtenerDumpNodo(), los valores de enumeración de cadenas se convierten a tipo primitivo string en lugar de mantener el tipo Enum.

La solución requiere modificaciones en tres niveles:

1. Extender el procesamiento de enumeraciones:

if (typeof CC_EDITOR !== 'undefined' && CC_EDITOR) {
  function procesarEnumExtendido(definicion) {
    if (definicion.__listaEnum__) {
      return definicion.__listaEnum__;
    }
    
    const elementos = [];
    let esString = false;
    
    Object.entries(definicion).forEach(([clave, valor]) => {
      esString = typeof valor === 'string';
      elementos.push({ nombre: clave, valor });
    });
    
    if (!esString) {
      elementos.sort((a, b) => a.valor - b.valor);
    }
    
    return definicion.__listaEnum__ = elementos;
  }
}

2. Corregir la serialización del editor:

function interceptarDumpNodo(funcionOriginal) {
  return function(nodo) {
    const resultado = funcionOriginal.call(this, nodo);
    
    if (resultado.valor && resultado.valor.__componetes__) {
      resultado.valor.__componetes__.forEach(componente => {
        const metadatos = resultado.tipos[componente.tipo];
        
        if (metadatos && metadatos.propiedades) {
          Object.keys(metadatos.propiedades).forEach(propKey => {
            const propiedad = metadatos.propiedades[propKey];
            if (propiedad.tipo === 'Enum') {
              componente.valor[propKey].tipo = 'Enum';
            }
          });
        }
      });
    }
    
    return resultado;
  };
}

3. Adaptar la interfaz de usuario:

function obtenerTipoReal(contexto) {
  if (contexto._tipoCacheado) {
    return contexto._tipoCacheado;
  }
  
  let tipoDato = 'string';
  try {
    const primerElemento = contexto._atributos.listaEnum[0];
    tipoDato = typeof primerElemento.valor;
  } catch (error) {
    console.warn('Error al determinar tipo de enumeración:', error);
  }
  
  contexto._tipoCacheado = tipoDato;
  return tipoDato;
}

function obtenerValorInput(contexto) {
  const tipoReal = obtenerTipoReal(contexto);
  const valorCrudo = contexto.$input.valor;
  
  return tipoReal === 'string' ? valorCrudo : Number(valorCrudo);
}

Adicionalmente, se debe suprimir la advertencia de validación original que rechaza valores no numéricos:

function filtrarAdvertenciasEnum(mensajeOriginal) {
  if (typeof mensajeOriginal === 'string' && 
      mensajeOriginal.startsWith('Valor numérico esperado para')) {
    return; // Suprimir advertencia para tipos extendidos
  }
  
  console.warn(mensajeOriginal);
}

Esta implementación permite utilizar enumeraciones de cadenas, flotantes y enteros en las propiedades de componentes:

export const EscalasDisponibles = {
  ESCALA_0_3: 0.3,
  ESCALA_0_5: 0.5,
  ESCALA_0_8: 0.8,
  ESCALA_1_0: 1.0,
  ESCALA_1_2: 1.2,
  ESCALA_1_5: 1.5,
  ESCALA_2_0: 2.0
};

export const IconosPredefinidos = {
  USUARIO: 'icono_usuario',
  CONFIGURACION: 'icono_config',
  BUSQUEDA: 'icono_busqueda'
};

Los componentes pueden declararse normalmente:

@ccclass('MiComponente')
class MiComponente extends cc.Component {
  @property({
    type: cc.Enum(EscalasDisponibles),
    tooltip: 'Factor de escala del componente'
  })
  factorEscala = EscalasDisponibles.ESCALA_1_2;
  
  @property({
    type: cc.Enum(IconosPredefinidos),
    tooltip: 'Icono representativo'
  })
  iconoActual = IconosPredefinidos.USUARIO;
}

Etiquetas: CocosCreator enumeraciones editor-propiedades JavaScript extension-editor

Publicado el 6-6 18:19