【问题标题】:Javascript Game - Waste Management between IIFEsJavascript 游戏 - IIFE 之间的废物管理
【发布时间】:2016-03-23 13:29:04
【问题描述】:

概述

大家好! - 我正在创建一个横向卷轴太空射击游戏(类似于旧游戏,仍在学习中!) - 我想知道如何更好地管理我的对象以防止资源浪费!

所以,在我的代码中,我为播放器创建了一个 IIFE,并为射弹创建了一个构造函数。玩家聆听游戏画布上的点击,当它听到点击时,它会创建一个弹丸并将其附加到玩家内的一个对象上。但是,当弹丸到达屏幕的右侧时,我希望它被销毁,以便从玩家的弹丸对象中移除,并让弹丸的所有更新和绘制功能结束。到目前为止,我已经设法停止它的绘制和更新,但我还不能将它从 Players projectile 对象中删除。希望下面的代码能更好地展示我正在尝试做的事情。

示例

var Player = (function () {
    var width = 50;
    var height = 50;
    var x = 0;
    var y = 0;
    var projectiles = [];

    var update = function () {
        for (var p = 0; p < projectiles.length; p++) {
            if(!projectiles[p].destroyed)projectiles[p].update();
        }
    };

    var draw = function () {
        Canvas.context.fillStyle = 'white';
        Canvas.context.fillRect(x, y, width, height);

        for (var p = 0; p < projectiles.length; p++) {
            if(!projectiles[p].destroyed)projectiles[p].draw();
        }
    };

    Canvas.bindEvent('mousemove', function (e) {
        //x = e.pageX - Canvas.element.getBoundingClientRect().left;
        y = e.pageY - Canvas.element.getBoundingClientRect().top;
    });

    Canvas.bindEvent('click', function () {
        projectiles.push(new Projectile(width, y + (height / 2)));
    });

    return {
        draw: draw,
        update: update
    }
})();

var Projectile = function (x, y) {
    this.w = 10;
    this.h = 10;
    this.x = x;
    this.y = y;
    this.speed = 5;
    this.destroyed = false;

    this.update = function () {
        this.x += this.speed;

        if(this.x > Canvas.element.width){
            this.destroyed = true;
            this.x = 0;
            console.log('Projectile Destroyed!');
        }
    };

    this.draw = function(){
        Canvas.context.fillStyle = 'red';
        Canvas.context.fillRect(this.x, this.y, this.w, this.h);
    };
};

Js 小提琴

这是我当前在一个半工作的 JS fiddle 中的代码,因此可以在上下文中查看上面的代码。如果这个问题不清楚,请在 cmets 中告诉我,我会尽力澄清。谢谢大家!

https://jsfiddle.net/tzzgwr1w/

【问题讨论】:

    标签: javascript performance canvas garbage


    【解决方案1】:

    您可以在更新方法中删除已损坏的,但您需要从数组末尾循环。

    var update = function () {
        for (var p = projectiles.length - 1; p >= 0; p--) {
            if(!projectiles[p].destroyed)projectiles[p].update();
            else projectiles.splice(p,1);
        }
    };
    

    假设您有一个从 0 到 X 的基本循环。 如果删除索引 0 处的元素,则数组将移动,这意味着索引 1 处的对象将位于索引 0 处。 但下一个循环将执行 i++,则不会检查位于索引 0 处的对象。

    【讨论】:

    • 您好,感谢您的回答! - 但是,我不清楚你的意思是需要从数组的末尾循环 - 你能澄清一下吗?
    • @Lewis 我编辑了我的答案,希望可以理解。
    • @Lewis 你应该看到 Vincent 回答关于销毁逻辑的一些优化
    • 拼接成本高。请参阅对文森特答案的评论。
    • @markE 同意忽略/重用的东西,我在我的游戏中做台球
    【解决方案2】:

    试试这个代码:

    var update = function () {
        for (var p = arr.length-1; p >= 0; p--) {
            projectiles[p].update();
            if (projectiles[p].destroyed) projectiles.splice(p, 1);
        }
    };
    

    这应该是对播放器函数/类中更新函数的小修改。更新射弹后,它会检查射弹是否将自己声明为已销毁,如果是,则将其移除。 splice 删除索引 p 处的元素。

    【讨论】:

    • destroyed 可以在 update 函数中设置为 true。这是考虑到这一点的唯一答案。
    • @A1rPun 是的,这意味着对象在内存中多停留 1 个刻度,但是这个答案不起作用,因为它在发生拼接时会丢失数组中的一些元素。
    • @Haketo 好点。 for (var p = projectiles.length; p--;)Vincent 在使用splice 时更好地使循环向后。
    • .splice 的成本相当高——通常比在没有破坏元素的情况下手动重建数组慢 50% 以上。拼接工作,但它只是一个“好的”解决方案。更好的解决方案是简单地忽略被破坏的元素(如果有必要,当destroyCount 变大时,通过手动重建数组来批量清理它们)。最好的解决方案是忽略被破坏的元素,同时将被破坏的元素重新用于新的射弹。
    【解决方案3】:

    在这里使用拼接有点浪费资源。我建议对阵列进行线性扫描。下面的算法将就地过滤弹丸数组:

    function removeDestroyeds(arr) {
      for (var i=0, j=0; j < arr.length; j++) {
        if (!arr[j].destroyed) {
          arr[i++] = arr[j];
        }
      }
      arr.length = i;
    }
    
    ....
    
    var update = function () {
        for (var p = 0; p < projectiles.length; p++) {
            projectiles[p].update();
        }
        removeDestroyeds(projectiles);
    };
    
    var draw = function () {
        Canvas.context.fillStyle = 'white';
        Canvas.context.fillRect(x, y, width, height);
    
        for (var p = 0; p < projectiles.length; p++) {
            projectiles[p].draw();
        }
    };
    
    ....
    

    更新:

    @Haketo 和我做了一个快速基准测试:http://jsperf.com/splice-nosplice/11

    【讨论】:

    • 好点——拼接成本高,赞成。但更好的是,忽略被摧毁的射弹并将它们的数组元素重新用于新的射弹。
    • @Haketo 有趣。首先我看到了你的基准,我想:“哦,他没有恢复数组状态,所以在第一次迭代后它没有删除”,这是真的,但是如果我在每次迭代中使用一个新数组,拼接方法似乎还要更快。有想过,一直以为splice是O(n)
    • @TamasHegedus 长凳并不真正相关我在写它时犯了一个错误(在对象创建中拼写错误)。算了,你的解决方案更快:)
    • @TamasHegedus 顺便说一句,准备代码在每次测试之前执行,因此数组在每次测试之前都保持不变,还是我错了?
    • @Haketo 准备代码在bench循环之前运行,所以迭代部分使用同一个数组实例。所以,顺便说一句,如果被摧毁的弹丸非常罕见(每次模型更新 0-1 弹丸),那么拼接确实更快。
    【解决方案4】:

    在您的更新循环中,您可以使用.splice() 移除被摧毁的弹丸。为了不跳过 projectiles 数组中的项目,您需要反向迭代。

    for (p = projectiles.length-1; p >= 0; p--) {
        if(projectiles[p].destroyed) {
            projectiles.splice(p, 1);
        } else {
            projectiles[p].update();
        }
    }
    

    【讨论】:

    • 您好,感谢您的回答! - 如果不反向循环,我不清楚你所说的跳过弹丸是什么意思?
    • 假设阵列中有 5 个射弹,第三个(索引 = 2)被摧毁,因此您想将其移除。当您使用.splice(2,1) 删除它时,紧随第三个项目(它的索引 = 3)的项目现在将移动以填充被删除项目留下的空槽,并且它的索引将更改为 2。在下一次迭代中在循环中,p 将等于 3,因此将永远不会检查移动到索引 2 的项目。
    • 如果你从数组的末尾开始,任何被移动的项目都已经被检查过了,所以你不必担心跳过任何一个。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-21
    • 1970-01-01
    • 2015-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多