Inheritence and OOP with PlayCanvas

Hello everyone,

Here I have a base class / script

var DialogueBase = pc.createScript('dialogueBase');

DialogueBase.attributes.add('portrait', {
    type:'entity',
    title:'Portrait'
});

DialogueBase.attributes.add('landscape', {
    type:'entity',
    title:'Landscape'
});

and many child classes, for example:

var QuickNavigationDialogue = pc.createScript('quickNavigationDialogue');

QuickNavigationDialogue.attributes.add('portrait', {
    type:'entity',
    title:'Portrait'
});

QuickNavigationDialogue.attributes.add('landscape', {
    type:'entity',
    title:'Landscape'
});

and

var ProductDialogue = pc.createScript('productDialogue');

ProductDialogue.attributes.add('portrait', {
    type:'entity',
    title:'Portrait'
});

ProductDialogue.attributes.add('landscape', {
    type:'entity',
    title:'Landscape'
});

so I want both “ProductDialogue” and “QuickNavigationDialogue” to inherit from “DialogueBase”
such that I remove the same attributes mentioned in all classes.

I found this post:

which has some useful info, but still can’t figure out what to do.
Mainly the syntax is confusing, we don’t have a constructor.
Any hints or examples please

PlayCanvas is more of a component based design: http://gameprogrammingpatterns.com/component.html

So rather than having a ‘base’, think of it as a collection of behaviours.

In this case, I would have the Dialogue as it’s own component with all the references and then have ProductDialogue keep a reference to the Dialogue internally. This will allow you to use common behaviour from the Dialogue while allowing for customised behaviour.

The orbit camera does something like this where the input controllers keep a reference to the Orbit Camera script: https://developer.playcanvas.com/en/tutorials/orbit-camera/

1 Like

I too would like to do what Vasken wants to do.

I understand yaustar 's point that PlayCanvas is entity based more than OOP based. I see that as a point of view and not a technical limitation. Thus, both entity-component architecture and OOP architecture can coexist. As a Unity Developer with 8 years of full-time experience, I cannot imagine creating a professional project without “extending MonoBehaviour” (the equivalent of the subject of this post). It would be a foolish restriction for Unity to ignore it and thus I think its a unnecessary restriction to impose on PlayCanvas.

So again, how can we accomplish Vasken 's request?

1 Like

You should look into using TypeScript. It has both advantages and drawbacks in the context of PlayCanvas, but one of the advantages is that TypeScript supports language features you’ll recognize from C# such as inheritance.

1 Like

Great. Is typescript compatible with PlayCanvas? What would the code or pseudocode look like to extend a PlayCanvas ‘script’?

Yes, TypeScript is a superset of JavaScript and works fine with PlayCanvas when it’s compiled to vanilla JavaScript. You can see the code to extend a script in the link I posted above.
With TypeScript you’ll work with an offline editor and upload the code to PlayCanvas. Currently, an official workflow for this hasn’t been released yet, but it’s coming. So, working with TypeScript right now requires a bit of a hack, but it does work and many developers think it’s worth the effort. We’re currently finalizing our third commercial game written in TypeScript, and all future productions will be in TypeScript as well.

1 Like

Great. Your example does not include PlayCanvas. How would your example connect with the existing api such as createScript?

For example, completely replace this code with code to make TheSuperClass as well as an inheriting TheSubClass. The following is PlayCanvas compliant code which compiles and runs.

var TheSuperClass= pc.createScript('theSuperClass');

TheSuperClass.prototype.initialize = function() {

};

https://developer.playcanvas.com/en/api/pc.html#createScript

Hi @srivello,

Upvote the following github issue, maybe we get it sooner! :smile:

I’m not 100% ES5 JavaScript supports inheritance in the same way you would expect in C++ or C#. There’s pc.extend that ‘merges’ two object prototypes together but not sure if that works with the scriptType system and I don’t think it allows you to call the base class functions.

I will have to give this a try myself and check :thinking:

You can check what the pc.createScript call does behind the scenes:

  • extending the script object with prototype methods and script attributes
  • registers the new script type to the application script registry

You could create an intermediate class in Typescript that handles the pc.createScript call and extend that class in your code, to keep everything pretty and in one place. Much like in Unity you usually extend the MonoBehaviour class.

@yaustar is right that classes in Javascript are in general syntactic sugar mainly (extending the prototype methods of an object).

Had a quick look and I don’t think you be able to do this out of the box with PlayCanvas and ES5.

You could write a new createScript function that creates a new ScriptType and effectively copies the attributes of the ‘base’ class and patches the prototype functions that are or would be overridden.

Edit: This is just me messing around to see what I could do (this is not an official example!) but you get the idea that a function could be written to ‘extend’ or ‘inherit’ from a base ScriptType

Just need to find a way to clone the attributes :thinking:

https://playcanvas.com/editor/scene/915259

Thanks @yaustar. That completely answers my original question. That’ll solve my immediate needs.

Other things…

  1. To copy the attributes, doesn’t the swap method do that for us? Can some portion of its underlying code be used to ‘copy attributes between two arbitrary classes’ to advance your demo project above?

Or as an alternative, if some list or iterator of the attributes was available, a new custom method could loop through those and ‘add’ each to the new object. I see such an iterator does not yet exist per this api - https://developer.playcanvas.com/en/api/pc.ScriptAttributes.html

  1. I would like to override methods too. If you/I think of how, a reply below will help the thread.
B.prototype.message2 = function() {
  console.log('B message 2');

  //How to call overridden function?
  super.message2();
};
  1. Is Super.message2() calling the base class function?

Yes. I would like to do in JS what is shown here in C#…

The second method in the accepted answer should work here: https://stackoverflow.com/questions/11854958/how-to-call-a-parent-method-from-child-class-in-javascript

you can also consider composition
as OP wrote

many child classes

also

“ProductDialogue” and “QuickNavigationDialogue” to inherit from “DialogueBase”

so I think composition would be better so then
DialogueBase has “ProductDialogue” and “QuickNavigationDialogue”

I wanted to implement inheritance as well and came up with this solution:

1  // BASE CLASS
2  var DialogueBase = pc.createScript('dialogueBase');
3
4  DialogueBase.prototype.show = function() {
5      // Do some generic stuff...
6      this.onShow();
7  }
8
9  DialogueBase.prototype.onShow = function() {
10     console.error('Not implemented');
11 }
12
13 DialogueBase.prototype.hide= function(args) {
14     // Do some generic stuff...
16 }
1  // CHILD CLASS
2  var NavigationDialogue = pc.createScript('navigationDialogue');
3
4  if (!DialogueBase)
5      var DialogueBase = function() {};
6
7  NavigationDialogue.prototype = Object.create(DialogueBase.prototype);
8  NavigationDialogue.prototype.constructor = NavigationDialogue;
9
10 NavigationDialogue.prototype.onShow = function() {
11     // Do some specific stuff that is needed...
12 }
13
14 NavigationDialogue.prototype.hide = function(args) {
15     // Some additional functionality before hide gets called...
16     DialogueBase.prototype.hide.call(this, args);
17 }
  • The function show() is inherited
  • The function onShow() is overwritten
  • The function hide() calls the base and adds some functionality in front

Lines 7-8 are what make the inheritance work. The prototype of the base class gets copied, replaces the prototype of the child class and its constructor gets renamed.

Lines 4-5 would not be needed if not for the editor. The editor tries to interpret each script individually and if it doesn’t find the base class object, there is no prototype and it fails -> the script cannot be parsed. To get around that, you could also just put all classes into one file.

Some things I found that need to be kept in mind are:

  • Should be self explanatory, but loading order of scripts is important. always load base classes before child classes
  • Attributes are inherited as well, but only as properties. Therefore they cannot be exposed in the editor unless you add them to every child.

It works great for us so far and have not encountered any serious issues.

7 Likes

Hello,

This is exactly the way to do in ES5 !! Be careful to manage your scripts loading order so your parent class is loaded before your children classes.

It would be nice to have ES6 support in the PC editor, ES6 syntax is closer to C# with extends and super keywords…

I see comments saying we shouldn’t use OOP since PC is entity/component based. I’m sorry, but OOP is still very useful in this case. And PC did an entity/component system similar to Unity and its MonoBehaviour, but both are not really well designed since you have logic in your component. Sometimes ago, someone write an excellent entity framework Ash - entity-component-system framework where entity are just properties and they are system to handle all logics. Unity did finally the same Entity Component System | Entities | 0.17.0-preview.42 ; but since it requires many classes, obviously it isn’t easy with an editor like PC/Unity. My 2 cents :wink:

I don’t think it would be too difficult to create a ECS with PlayCanvas where the scriptTypes just hold data and handle events from the engine while the systems can hook onto the script system update event.

The most difficult part would be handling the query that many ECS libraries do quite well. I’ve been tempted to look into GitHub - ecsyjs/ecsy: Entity Component System for javascript or GitHub - fritzy/ape-ecs: Entity-Component-System library for JavaScript. and see how complex it would be to hook it into PlayCanvas

2 Likes

It’s also possible to add a new ECS with PlayCanvas’ framework as seen here with the Spine runtime: GitHub - playcanvas/playcanvas-spine: Plugin component for PlayCanvas which enables support for Spine animations.