Jump to content

Component based architecture in Typescript


gcardozo
 Share

Recommended Posts

Hi, Phaser devs!

I'm developing a game using Phaser with TypeScript and what I'm trying to achieve is this:
var component:AwesomeComponent = actor.getComponent<AwesomeComponent>();

I know there's a workaround by constructing objects of type T. But I don't like it because I'd probably need to define tons of different constructors to represent all differents kinds of components.

What I'm doing so far is passing a string with the type (of course a number would be better, but I'm still prototyping):

getComponent<T>(componentType:string): T 
{
    for (let i = 0; i < this.components.length; i++) 
    {
        if (componentType == this.components[i].componentType)
        {
            return <T><any>this.components[i];
        }
    }
    return undefined;
}

It works, but the result is not good, you have to type the class name twice:
var animComp = actor.getComponent<AnimationComponent>("AnimationComponent");

Any suggestions on how to solve it? I know there`s a way to do it, because Unity does exactly that in JavaScript.
Thank you for your time!

Link to comment
Share on other sites

I'm using something similar to this in my project.  It uses the Component constructors then casts up to the type after verifying it's an instance of the type.  Here's a minimal example...

What?  No TypeScript support in code snippets?

class Component {
    public actor: Actor;
}

interface ComponentType<T extends Component> { new(...args: any[]): T }

class Actor {
    public components: Component[] = [];

    private getComponentIndex<T extends Component>(type: ComponentType<T>): number {
        for (let i = 0; i < this.components.length; ++i) {
            const component: Component = this.components[i];
            if (component instanceof type) {
                return i;
            }
        }
        return -1;
    }

    public hasComponent<T extends Component>(type: ComponentType<T>): boolean {
        return this.getComponentIndex(type) !== -1;
    }

    public getComponent<T extends Component>(type: ComponentType<T>, ...args: any[]): T {
        const index: number = this.getComponentIndex(type);
        if (index !== -1) {
            return this.components[index] as T;
        }
        return this.addComponent(type, ...args);
    }

    public addComponent<T extends Component>(type: ComponentType<T>, ...args: any[]): T {
        if (this.hasComponent(type)) { return this.getComponent(type); }
        const component: T = new type(...args); // this needs ES5
        component.actor = this;
        this.components.push(component);
        return component;
    }

    public delComponent<T extends Component>(type: ComponentType<T>): T {
        const component: Component = this.getComponent(type);
        this.components.splice(this.components.indexOf(component), 1);
        component.actor = undefined;
        return component as T;
    }
}

class AwesomeComponent extends Component {
    public message: string;
    constructor(message: string) {
        super();
        this.message = message;
    }
    public alert(): void {
        console.log(this.message);
    }
}

function test(): void {
    const actor: Actor = new Actor();
    if (actor.hasComponent(AwesomeComponent)) { throw new Error(); }
    actor.addComponent(AwesomeComponent, "awesome!");
    const awesome: AwesomeComponent = actor.getComponent(AwesomeComponent); // automatic cast!
    awesome.alert();
    actor.delComponent(AwesomeComponent);
    if (actor.hasComponent(AwesomeComponent)) { throw new Error(); }
}

test();

 

Link to comment
Share on other sites

  • 4 weeks later...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...