How To Use PlayCanvas Enums?

Hey everyone, could someone shed some light on how PlayCanvas’s enums work? In the example below, I’m unable to print out or link to enum values. I’m only able to print out the chosen enum, but not the other values used for comparisons.

You can also view the example project here.

var EnumTest = pc.createScript('enumTest');

EnumTest.attributes.add('Choices', {
    type: 'enum',
    enum: [
        { 'FirstChoice': 1 },
        { 'SecondChoice': 2 },
        { 'ThirdChoice': 3 }
    ], 
    default : 3
});

// initialize code called once per entity
EnumTest.prototype.initialize = function() {
    
    //Correctly prints '3' in the browser window, no problem here
    console.log( "Choices set to = " + this.Choices);
    
    //Expecting this to print '1' in our browser console if the syntax is correct, testing shows a null value
    console.log( "First Choice = " + this.Choices.FirstChoice );
    
    //Should pass the enumerated value to the function that will then print it out, but instead it's passing in a null value so comparison does not work
    this.PrintEnumValue( this.Choices.FirstChoice );
    
};

EnumTest.prototype.PrintEnumValue = function( choice )
{
  
    if( choice == this.Choices.FirstChoice )
    {
        console.log( "PrintEnumValue() choice successfully equated to this.Choices.FirstChoice... choice = " + choice + ", this.Choices.FirstChoice = " + this.Choices.FirstChoice );
    }
    else if( choice == this.Choices.SecondChoice )
    {
        console.log( "PrintEnumValue() choice successfully equated to this.Choices.SecondChoice... choice = " + choice + ", this.Choices.SecondChoice = " + this.Choices.SecondChoice );
    }
    else if( choice == this.Choices.ThirdChoice )
    {
        console.log( "PrintEnumValue() choice successfully equated to this.Choices.ThirdChoice... choice = " + choice + ", this.Choices.ThirdChoice = " + this.Choices.ThirdChoice );
    }
    else
    {
        console.log( "PrintEnumValue() choice not equated to any values from list of enums" );
    }
    
};
1 Like

Hey,
They work easier than you think :wink:

You don’t need to specify its type as enum. The enum clause is doing that for you already. Instead you need to set the type to the type of data, which you would like to access, like in your case: number

Test.attributes.add('Choices', {
    type: 'number',
    enum: [
        { 'FirstChoice': 1 },
        { 'SecondChoice': 2 },
        { 'ThirdChoice': 3 }
    ],
    default: 1
});

The first parameter of your enum is its display title, the second one references the actual value.

Therefore, if you would like to access its second item you need to access it like this:
this.Choices[1]

Or if you like to use strings for the title & reference it would be:
This.Choices[‘FirstChoice‘]

Hope that helps.

Greetings,

Martin

2 Likes

Thanks Martin, I modified the enums in that example to try to access it using the string notation. However the same null reference persists, as well as a new editor warning that I should be using the original dot notation.

console.log( "First Choice = " + this.Choices[ 'FirstChoice' ] );

Alert in editor says … " [‘FirstChoice’] is better written with dot notation" for that line

Any ideas? I modified the example project to show the alert and that the null reference persists.

Hey Derrick,
yep that’s correct you can use the dot notation as well. It’s a bit less to write, but both ways should work.

The reason why you will get a null reference, is because you are going a level too deep.
this.Choices[ 'FirstChoice' ] returns null, because it is not containing anything.
this.Choices is the object, that is containing your selection. Don’t get irritated by the fact, that you have two columns in the enum definition, as the first is only representing its title for the editor.

So, if you want to get your current selection you only need to write:

console.log( "Selected Choice = " + this.Choices );

If you want to get a list of all choices you could either loop through the enum or append it with a comma instead of a plus. That will return the object, instead of a string, so you can click on it in your browser console to see its content. Like so:

console.log( "Choices:", this.Choices );

If you want to check in your code for a specific selection you can do so by:

if( this.choices === 'FirstChoice')

or with a switch case :wink:

Would you mind trying out the demo project? I’ve modified it based on your feedback with no changes to the output.

I’m able to print the specific selection without problems, but all of the other tests fail, including printing the object variable using comma concatenation to a console log string, which simply prints the number value.

var EnumTest = pc.createScript('enumTest');

EnumTest.attributes.add('Choices', {
    type: 'number',
    enum: [
        { 'FirstChoice': 1 },
        { 'SecondChoice': 2 },
        { 'ThirdChoice': 3 }
    ], 
    default : 3
});

// initialize code called once per entity
EnumTest.prototype.initialize = function() {
    
    //Correctly prints the current selection made in the editor
    console.log( "Printing this.Choices = " + this.Choices);
    
    //Should print the enum object using comma notation
    console.log( "Choices object = " , this.Choices );
    
    //Should print the enum choice as a string, but prints the number value
    console.log( "String(this.Choices) = " + String(this.Choices) );
    
    //Expecting these two console logs to print the value of the enum, should be 1, but prints a null value
    console.log( "First Choice printed with string notation = " + this.Choices[ 'FirstChoice' ] );
    console.log( "First Choice printed with dot notation = " + this.Choices.FirstChoice );
    
    //Should pass the enumerated value to the function that will then print it out, but instead it's passing in a null value so comparison does not work
    this.PrintEnumValue( this.Choices.FirstChoice );
    
    //Same as above, using string notation, also doesn't work, still null
    this.PrintEnumValue( this.Choices[ 'FirstChoice' ] );
};

EnumTest.prototype.PrintEnumValue = function( choice )
{
  
    //In current testing choice is null
    if( !choice )
    {
        console.log( "PrintEnumValue() choice is null, cannot complete comparison check" );
    }
    else if( choice === this.Choices.FirstChoice )
    {
        console.log( "PrintEnumValue() choice successfully equated to this.Choices.FirstChoice... choice = " + choice + ", this.Choices.FirstChoice = " + this.Choices.FirstChoice );
    }
    else if( choice === this.Choices.SecondChoice )
    {
        console.log( "PrintEnumValue() choice successfully equated to this.Choices.SecondChoice... choice = " + choice + ", this.Choices.SecondChoice = " + this.Choices.SecondChoice );
    }
    else if( choice === this.Choices.ThirdChoice )
    {
        console.log( "PrintEnumValue() choice successfully equated to this.Choices.ThirdChoice... choice = " + choice + ", this.Choices.ThirdChoice = " + this.Choices.ThirdChoice );
    }
    else
    {
        console.log( "PrintEnumValue() choice not equated to any values from list of enums" );
    }
    
};

You’ve kinda missed the point on enums and how they are defined there.

this.Choices will be a value of 1 2 or 3. There is no such thing as this.Choices.FirstChoice. (this.Choices is just a number, as that’s the type you defined). FirstChoice is a member of an anonymous array. Personally I wish it was configured differently and that the enum was a POJO with key value pairs as then you could have a reference to it, but it isn’t. This is configuration for the editor and not for the code. I don’t believe you can access the values you defined so you either need to test for the actual value - 1 2 or 3, or you also need to define your code enum elsewhere.

In my code I have a helper function to convert a POJO to the PlayCanvas format for enum, this allows me to access those values if I need them.

var MyEnum = {
    FirstChoice: 1,
    SecondChoice: 2,
    ThirdChoice: 3
};

function mapEnum(enum) {
    var reverseLookup = {};
    var result = [];
    for(var value in enum) {
         if(!enum.hasOwnProperty(value)) continue;
         var enumEntry = {};
         enumEntry[value] = enum[value];
         result.push(enumEntry);
         reverseLookup[enum[value]] = value;
    }
    enum.toString = function(value) { return reverseLookup[value]; };
    return result;
}

EnumTest.attributes.add('Choices', {
    type: 'number',
    enum: mapEnum(MyEnum),
    default: MyEnum.ThirdChoice
});

EnumTest.prototype.initialize = function() {
     console.log(this.Choices); //3
     console.log(MyEnum.toString(this.Choices)); //ThirdChoice
     switch(this.Choices) {
           case MyEnum.FirstChoice:
                // do something
               break;
           case MyEnum.SecondChoice:
               // ...
              break;
     }
}

Note the toString only works on primitive values - if you want it to work on object values then it needs to use Map and be transpiled/polyfilled for older browsers with Babel etc.

Second note, if you are happy monkey patching attributes.add then what I do is something like:

// enum.js
;(function() {
  function mapEnum(enum) {
    var reverseLookup = {};
    var result = [];
    for(var value in enum) {
         if(!enum.hasOwnProperty(value)) continue;
         var enumEntry = {};
         enumEntry[value] = enum[value];
         result.push(enumEntry);
         reverseLookup[enum[value]] = value;
    }
    enum.toString = function(value) { return reverseLookup[value]; };
    return result;
  }

     var add = pc.ScriptAttributes.prototype.add;
     pc.ScriptAttributes.prototype.add = function(name, definition) {
          if(definition.enum && !Array.isArray(definition.enum)) {
              definition.enum = mapEnum(definition.enum);
          }
          return add(name, definition);
     }
})();

// My Script.js

var MyEnum = {
    FirstChoice: 1,
    SecondChoice: 2,
    ThirdChoice: 3
};

EnumTest.attributes.add('Choices', {
    type: 'number',
    enum: MyEnum,
    default: MyEnum.ThirdChoice
});

You need to set the priority of enum.js before any of your scripts etc.

Thanks for the code on patching addtributes.add whydoidoit, in the test project I created a new script called EnumHelper.js with the code and made sure it is set to load first in the editor.

However, on the line that reads…

var add = pc.ScriptAttributes.prototype.add;

The code has an error when run, stating that pc.ScriptAttributes in null (and indeed, when I check what ‘pc’ contains, there is no variable called ScriptAttributes.

Any idea on how to fix the error?

var EnumHelper = pc.createScript('enumHelper');

//Created by whydoidoit from the PlayCanvas forums
//https://forum.playcanvas.com/t/how-to-use-playcanvas-enums/4999/7

//Extends the functionality of PlayCanvas's enums to behave more like Java's enum,
//Simple add this script to your project, and within PlayCanvas's editor set this script to run first.
//Then to use enums in the future you have to declare your enum as a standard javascript enum before creating the public one
/*
 
 var Choices = {
    FirstChoice: 1,
    SecondChoice: 2,
    ThirdChoice: 3
 };
 
 EnumTest.attributes.add('Choice', {
    type: 'number',
    enum: Choices, 
    default : Choices.FirstChoice
}); 
*/


;(function() 
{
    
    //-------------------------------------------//
    function mapEnum(enumVar)
    //-------------------------------------------//
    {
        var reverseLookup = {};
        var result = [];
      
        for(var value in enumVar) 
        {
             if(!enumVar.hasOwnProperty(value)) continue;
            
             var enumEntry = {};
             enumEntry[value] = enumVar[value];
             result.push(enumEntry);
             reverseLookup[enumVar[value]] = value;
        }

        enumVar.toString = function(value) { return reverseLookup[value]; };
        return result;

    } //END MapEnum function
    
    
    
    var add = pc.ScriptAttributes.prototype.add;
    
    //---------------- ADD -----------------------------------------//
    pc.ScriptAttributes.prototype.add = function(name, definition) 
    //--------------------------------------------------------------//
    {
          if(definition.enum && !Array.isArray(definition.enum)) 
          {
              definition.enum = mapEnum(definition.enum);
          }
         
          return add(name, definition);
         
    }; //END Add To Enum Functionality To ScriptAttributes enum
     
})();

Ah damn, yeah in the standard one it would have to use this I think:

;(function() {
  var dummy = pc.createScript('dummy');
  function mapEnum(enum) {
    var reverseLookup = {};
    var result = [];
    for(var value in enum) {
         if(!enum.hasOwnProperty(value)) continue;
         var enumEntry = {};
         enumEntry[value] = enum[value];
         result.push(enumEntry);
         reverseLookup[enum[value]] = value;
    }
    enum.toString = function(value) { return reverseLookup[value]; };
    return result;
  }

     var add = dummy.attributes.prototype.add;
     dummy.attributes.prototype.add = function(name, definition) {
          if(definition.enum && !Array.isArray(definition.enum)) {
              definition.enum = mapEnum(definition.enum);
          }
          return add(name, definition);
     }
})();