2

I'm doing a game using Phaser with TypeScript. I want to implement a component based architecture for Actors. 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?

Thank you for your time!

1 Answers1

0

I've got some good suggestions on Phaser forum, the solution shown by user @flyovergames was what I was looking for:

<script src="https://raw.githubusercontent.com/photonstorm/phaser/master/v2/src/Phaser.js"></script>
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 full thread: Solution