Компонентных систем для JavaScript игры


Я создаю на базе JavaScript/WebGL, в игре и хочу, чтобы вы взгляните на текущие компонент системы образования, которое я создал для нее. Я использовал его один раз, прежде чем на другой проект, и хотя я был очень доволен результатами, там были части я не отношусь на 100%.

В частности, иногда компонент должен вызвать attachedEntities версия функции переопределены. Например handleInput может вызвать attachedEntities handleInput функции, а затем с новой movementVector умножить на минус 1, так что теперь все ввод, этот компонент активно восстанавливается.

Когда мне наперерез свойства, я в настоящее время обертывание перехваченные вызовы функций на закрытие (см. basecomponent.перехват()), так что этот объект можно сослаться на сам компонент. Без что этот объект до сих пор называют attachedEntity (из компонентов версии этой функции), поэтому для получения компонентов свойства мне пришлось выполнить запрос.

Вещи, которые я использовал в качестве ссылки:

Полный исходный код можно найти здесь.

Базовый Компонент

(function() {
ChuClone.namespace("ChuClone.components");
ChuClone.components.BaseComponent = function() {
    this.interceptedProperties = {};
    return this;
};

ChuClone.components.BaseComponent.prototype = {
    /**
     * Array of properties intercepted, this is used when detaching the component
     * @type {Array}
     */
    interceptedProperties    : null,
    /**
     * @type {ChuClone.GameEntity}
     */
    attachedEntity            : null,
    /**
     * @type {Number}
     */
    detachTimeout            : 0,
    /**
     * Unique name for this component
     * @type {String}
     */
    displayName                : "BaseComponent",

    /**
     * If a component can stack, then it doesn't matter if it's already attached.
     * If it cannot stack, it is not applied if it's currently active.
     * For example, you can not be frozen after being frozen.
     * However you can be sped up multiple times.
     * @type {Boolean}
     */
    canStack                : false,

    /**
     * Attach the component to the host object
     * @param {ChuClone.GameEntity} anEntity
     */
    attach: function(anEntity) {
        this.attachedEntity = anEntity;
    },

    /**
     * Execute the component
     * For example if you needed to cause an animation to start when a character is 'unfrozen', this is when you would do it
     */
    execute: function() {

    },

    /**
     * Detaches a component from an 'attachedEntity' and restores the properties
     */
    detach: function() {
        clearTimeout(this.detachTimeout);
        this.restore();

        this.interceptedProperties = null;
        this.attachedEntity = null;
    },

    /**
     * Detach after N milliseconds, for example freeze component might call this to unfreeze
     * @param {Number} aDelay
     */
    detachAfterDelay: function(aDelay) {
        var that = this;
        this.detachTimeout = setTimeout(function() {
            that.attachedEntity.removeComponentWithName(that.displayName);
        }, aDelay);
    },

    /**
     * Intercept properties from the entity we are attached to.
     * For example, if we intercept handleInput, then our own 'handleInput' function gets called.
     * We can reset all the properties by calling, this.restore();
     * @param {Array} arrayOfProperties
     */
    intercept: function(arrayOfProperties) {
        var len = arrayOfProperties.length;
        var that = this;
        while (len--) {
            var aKey = arrayOfProperties[len];
            this.interceptedProperties[aKey] = this.attachedEntity[aKey];

            // Wrap function calls in closure so that the 'this' object refers to the component, if not just overwrite
            if(this.attachedEntity[aKey] instanceof Function) {
                this.attachedEntity[aKey] = function(){
                    that[aKey].apply(that, arguments);
                }
            } else {
                this.attachedEntity[aKey] = this[aKey];
            }

        }
    },

    /**
     * Restores poperties that were intercepted that were intercepted.
     * Be sure to call this when removing the component!
     */
    restore: function() {
        for (var key in this.interceptedProperties) {
            if (this.interceptedProperties.hasOwnProperty(key)) {
                this.attachedEntity[key] = this.interceptedProperties[key];
            }
        }
    }
}
 })();

JumpComponent

   (function(){
    ChuClone.namespace("ChuClone.components");

    ChuClone.components.JumpPadComponent = function() {
        ChuClone.components.JumpPadComponent.superclass.constructor.call(this);
        console.log(this)
    };

    ChuClone.components.JumpPadComponent.prototype = {
        displayName                     : "JumpPadComponent",                   // Unique string name for this Trait

        _textureSource                  : "assets/images/game/jumppad.png",
        _restitution                    : 2,
        _previousMaterial               : null,
        _previousRestitution            : 0,

        /**
         * @inheritDoc
         */
        attach: function(anEntity) {
            ChuClone.components.JumpPadComponent.superclass.attach.call(this, anEntity);

            var view = anEntity.getView();
            var body = anEntity.getBody();

            // Swap materials
            this._previousMaterial = view.materials[0];
            view.materials[0] = new THREE.MeshLambertMaterial( {
                color: 0xFFFFFF, shading: THREE.SmoothShading,
                map : THREE.ImageUtils.loadTexture( this._textureSource )
            });

            view.materials[0] = new THREE.MeshBasicMaterial( { color: 0x608090, opacity: 0.5, wireframe: true } );

            // Swap restitution
            this.swapRestitution( body );


            // Listen for body change
            this.intercept(['setBody', 'height']);
        },

        /**
         * Sets the restitution level of  the provided body's fixtures to make it a jumppad
         * @param {Box2D.Dynamics.b2Body} aBody
         */
        swapRestitution: function( aBody ) {
            var node = aBody.GetFixtureList();
            while(node) {
                var fixture = node;
                node = fixture.GetNext();

                this._previousRestitution = fixture.GetRestitution();
                fixture.SetRestitution( this._restitution );
            }
        },

        /**
         * Set the body
         * @param {Box2D.Dynamics.b2Body} aBody
         */
        setBody: function( aBody ) {
            this.interceptedProperties.setBody.call(this.attachedEntity, aBody );
            if(aBody) // Sometimes setBody is called with null
                this.swapRestitution( aBody )
        },

        /**
         * Restore material and restitution
         */
        detach: function() {
            this.attachedEntity.getView().materials[0] = this._previousMaterial;

            var node = this.attachedEntity.getBody().GetFixtureList();
            while(node) {
                var fixture = node;
                node = fixture.GetNext();
                fixture.SetRestitution(this._previousRestitution);
            }

            ChuClone.components.JumpPadComponent.superclass.detach.call(this);
        }

    };

    ChuClone.extend( ChuClone.components.JumpPadComponent, ChuClone.components.BaseComponent );
})();

Сущность компонентов

    /**
     * Adds and attaches a component, to this entity
     * @param {ChuClone.components.BaseComponent}  aComponent
     * @return {ChuClone.components.BaseComponent}
     */
    addComponent: function(aComponent) {
        // Check if we already have this component, if we do - make sure the component allows stacking
        var existingVersionOfComponent = this.getComponentWithName(aComponent.displayName);
        if (existingVersionOfComponent && !existingVersionOfComponent.canStack) {
            return false;
        }

        // Remove existing version
        if (existingVersionOfComponent) {
            this.removeComponentWithName(aComponent.displayName);
        }


        this.components.push(aComponent);
        aComponent.attach(this);

        return aComponent;
    },

    /**
     * Convenience method that calls ChuClone.GameEntity.addComponent then calls execute on the newly created component
     * @param {ChuClone.components.BaseComponent}  aComponent
     * @return {ChuClone.components.BaseComponent}
     */
    addComponentAndExecute: function(aComponent) {
        var wasAdded = this.addComponent(aComponent);
        if (wasAdded) {
            aComponent.execute();
            return aComponent;
        }

        return null;
    },

    /**
     * Returns a component with a matching .displayName property
     * @param aComponentName
     */
    getComponentWithName: function(aComponentName) {
        var len = this.components.length;
        var component = null;
        for (var i = 0; i < len; ++i) {
            if (this.components[i].displayName === aComponentName) {
                component = this.components[i];
                break;
            }
        }
        return component;
    },

    /**
     * Removes a component with a matching .displayName property
     * @param {String}  aComponentName
     */
    removeComponentWithName: function(aComponentName) {
        var len = this.components.length;
        var removedComponents = [];
        for (var i = 0; i < len; ++i) {
            if (this.components[i].displayName === aComponentName) {
                removedComponents.push(this.components.splice(i, 1));
                break;
            }
        }

        // Detach removed components
        if (removedComponents) {
            i = removedComponents.length;
            while (i--) {
                removedComponents[i].detach();
            }
        }
    },

    /**
     * Removes all components contained in this entity
     */
    removeAllComponents: function() {
        var i = this.components.length;
        while (i--) {
            this.components[i].detach();
        }

        this.components = [];
    }

Использование

var jumpPadComponent = new ChuClone.components.JumpPadComponent();
entity.addComponentAndExecute( jumpPadComponent );

//... Some time later
entity.removeComponentWithName( ChuClone.components.JumpPadComponent.prototype.displayName );  

Мне любопытно про чужие мысли на этот счет реализации такого компонента системы, основанной на JavaScript для игры.

JumpPadComponent добавленные в пару стандартных сущностей:

JumpPad component toggle



Комментарии
1 ответ

Ваш код требует массу знаний в предметной области для надлежащего рассмотрения. Я пошел в свой репозиторий GitHub, чтобы попробовать и быть более знакомы с тем, что у вас есть.

Сущность компонентов

Вы спросили конкретно для этого, и я нашел некоторые вещи, чтобы задуматься.

addComponent


  • existingVersionOfComponent -> лучше имя может быть existingInstanceofComponent или даже проще экземпляра, так как нет контроля версий.

  • // Удалить существующую версию, для меня это смущает, что компонент стека, но вы по-прежнему позволяют только 1 экземпляр. Может быть, вы должны найти лучшее название/дескриптор, чем стек.

addComponentAndExecute


  • Вы возвращаете значение null здесь , не плохо само по себе, за исключением того, что вы вернуться ложным в addComponent. Вы, возможно, захотите пересмотреть ваш код и стандартизации на ложных или нуль.

removeComponentWithName


  • Этот код меня смущает. Кажется, построены несколько экземпляров одного и того же компонента в виду, но потом он ломается из за петли. Похоже, вы могли бы написать этот код с 1 петли.

  • Вы можете объединить 2 петли здесь в 1 петлю:

    removeComponentWithName: function(aComponentName) {
    var len = this.components.length;
    var removedComponents = [];
    for (var i = 0; i < len; ++i) {
    if (this.components[i].displayName === aComponentName) {
    removedComponents.push(this.components.splice(i, 1));
    break;
    }
    }

    // Detach removed components
    if (removedComponents) {
    i = removedComponents.length;
    while (i--) {
    removedComponents[i].detach();
    }
    }
    },

    может быть

    removeComponentWithName: function(componentName) {
    var len = this.components.length,
    removedComponent;
    for (var i = 0; i < len; ++i) {
    if (this.components[i].displayName === componentName) {
    removedComponent = this.components.splice(i, 1).pop();
    removedComponent.detach();
    break;
    }
    }
    },

5
ответ дан 27 января 2014 в 09:01 Источник Поделиться

Другие вопросы по теме
Генерация строк таблицы на форме с помощью JS
4 июля 2011 в 10:07
В JavaScript динамически