LoginSignup
5
2

More than 5 years have passed since last update.

HTML5ゲームエンジンの徹底評価[日本語訳] (7) パフォーマンス比較編

Last updated at Posted at 2018-12-20

パフォーマンス

ベンチマークのために、2Dゲームエンジン向けのレンダリングのみ負荷テストをしました。
同じ数の同じ内容の描画オブジェクトでテストをしました。公平にテストをするために、同じコンピュータ・同じバージョンのChromeブラウザでテストをしました。ゲーム画面のサイズは 800*600、画像も同じものを使いました。同じ画面で各エンジンに5,000、10,000、20,000個の描画オブジェクトのレンダリングをさせました。

craftyjsエンジンはレンダリングに問題があったために比較データをとれませんでした。QuintusはWebGLレンダリングに対応していないので、今回は比較データはとれませんでした。Phaserの描画コアはPixi.jsを利用しています。PhaserのデータはPixi.jsの結果のものです。各エンジン用に書いたテストコードはほぼ同一のものです。起動時に必要な量の描画オブジェクトを生成します。各フレームにて各オブジェクトを回転させています。

テストコード

pixi.js

var renderer = PIXI.autoDetectRenderer(800, 600,{backgroundColor : 0x1099bb});
document.body.appendChild(renderer.view);
var stage = new PIXI.Container();
var texture = PIXI.Texture.fromImage('bunny.jpg');
var tnum = 5000;
console.log("render Object Number:",tnum);
var bunnys = [];
for(var i=0;i<tnum;i++)
{
    var bunny = new PIXI.Sprite(texture);
    bunny.position.x = Math.random()*800;
    bunny.position.y = Math.random()*600;
    stage.addChild(bunny);
    bunnys.push(bunny);
}

animate();

function animate() {
    requestAnimationFrame(animate);

    for(var i=0;i<tnum;i++)
    {
       bunnys[i].rotation += 0.1;
    }
    renderer.render(stage);
}

Egret

class Main extends egret.DisplayObjectContainer {

    public constructor() {
        super();
        this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    }

    private tnum:number = 100000;
    private bunnys:egret.Bitmap[] = [];

    private onAddToStage(event:egret.Event) 
    {
        console.log("render Object Number:",this.tnum);
        this.stage.dirtyRegionPolicy = egret.DirtyRegionPolicy.OFF;
        RES.getResByUrl('resource/bunny.jpg',this.onComplete,this,RES.ResourceItem.TYPE_IMAGE);
    }

    private onComplete(event:any)
    {
        var img:egret.Texture = <egret.Texture>event;
        for(var i:number=0;i<this.tnum;i++)
        {
            var bunny = new egret.Bitmap(img);
            bunny.x = Math.random()*800;
            bunny.y = Math.random()*600;
            this.addChild(bunny);
            this.bunnys.push(bunny);
        }
        this.addEventListener(egret.Event.ENTER_FRAME, this.animate,this);
    }

    private animate(evt:egret.Event)
    {
        for(var i:number=0;i<this.tnum;i++)
        {
            this.bunnys[i].rotation += 1;
        }
    }
}

enchant.js

enchant();

window.onload = function () {
   var game = new Game(800, 600); 
   game.fps = 60;
   game.preload('bunny.jpg');
   game.onload = function() {
    var tnum = 100000;
    console.log("render Object Number:",tnum);
    var bunnys = [];
      var scene = new Scene();
      game.pushScene(scene);
      for(var i=0;i<tnum;i++)
        {
            var sprite = new Sprite(50, 50);
            sprite.image = game.assets['bunny.jpg'];
            sprite.x = Math.random()*800;
            sprite.y = Math.random()*600;
            scene.addChild(sprite);
            bunnys.push(sprite);
        }

        game.addEventListener('enterframe', function() {
          for(var i=0;i<tnum;i++)
            {
               bunnys[i].rotation += 1;
            }
        });
   };

   game.start();

};

Turbulenz

TurbulenzEngine = WebGLTurbulenzEngine.create({
    canvas: document.getElementById("canvas")
});

var graphicsDevice = TurbulenzEngine.createGraphicsDevice({});
var draw2D = Draw2D.create({
    graphicsDevice: graphicsDevice
});

var bgColor = [1.0, 1.0, 0.0, 1.0];
var tnum = 50000;

console.log("render Object Number:", tnum);
var bunnys = [];
for (var i = 0; i < tnum; i++) {
    var sprite = Draw2DSprite.create({
        width: 50,
        height: 50,
        x: Math.random() * 800,
        y: Math.random() * 600,
        color: [1.0, 1.0, 1.0, 1.0],
        rotation: Math.PI / 4
    });

    bunnys.push(sprite);
}

var texture = graphicsDevice.createTexture({
    src: "bunny2.jpg",
    mipmaps: true,
    onload: function (texture) {
        if (texture) {
            for (var i = 0; i < tnum; i++) {
                var sprite = bunnys[i];
                sprite.setTexture(texture);
                sprite.setTextureRectangle([0, 0, texture.width, texture.height]);
            }
        }
    }
});

var PI2 = Math.PI * 2;
var rotateAngle = PI2 / 360; // 1 deg per frame

function update() {
    if (graphicsDevice.beginFrame()) {
        graphicsDevice.clear(bgColor, 1.0);
        draw2D.begin();

        for (var i = 0; i < tnum; i++) {
            var sprite = bunnys[i];
            sprite.rotation += rotateAngle;
            sprite.rotation %= PI2; // Wrap rotation at PI * 2
            draw2D.drawSprite(sprite);
        }

        draw2D.end();

        graphicsDevice.endFrame();
    }
}



function render() {
    var tnum = 5000;
    console.log("render Object Number:", tnum);
    for (var i = 0; i < tnum; i++) {
        sprite.position.x = Math.random() * 800;
        sprite.position.y = Math.random() * 600;
    }
}

cocos2d-js

window.onload = function(){
    cc.game.onStart = function(){
        //load resources
        cc.LoaderScene.preload(["bunny.jpg"], function () {
            var tnum = 100000;
              console.log("render Object Number:",tnum);
              var bunnys = [];
            var MyScene = cc.Scene.extend({
                onEnter:function () {
                    this._super();
                    var batchNode = cc.SpriteBatchNode.create("bunny.jpg");
                    this.addChild(batchNode);
                    for(var i=0;i<tnum;i++)
                    {
                      var sprite = cc.Sprite.create("bunny.jpg");
                      sprite.setPosition((Math.random()*800), (Math.random()*600));
                      batchNode.addChild(sprite);
                      bunnys.push(sprite);
                    }
                    this.scheduleUpdate();
                },

                update:function () {
                  for(var i=0;i<tnum;i++)
                    {
                      bunnys[i].setRotation(bunnys[i].getRotation()+1);
                    }
                    this.scheduleUpdate();
                }
            });
            cc.director.runScene(new MyScene());
        }, this);
    };
    cc.game.run("gameCanvas");
};

melonJS

var PlayScreen = me.ScreenObject.extend( {
    onResetEvent: function() {
        me.game.world.addChild(new me.ColorLayer("background", "#5E3F66", 0), 0);
        for (var i = 0; i < 5000; i++) {
            me.game.world.addChild(new Smilie(i), 3);
        }
    }
});

var Smilie = me.Sprite.extend({
    init : function (i) {
        this._super(
            me.Sprite,
            "init",
            [
                (-15).random(800),
                (-15).random(600),
                {
                    image: me.loader.getImage(game.assets[0].name)
                    ,width : 50
                    ,height : 50
                }
            ]
        );
        this.rotation = 0;
        this.alwaysUpdate = true;
    },

    update : function () {
        this.rotation += 3/180*Math.PI;
        this.angle = this.rotation ;
        return true;
    },}  );

Hilo

function init(){
    var stage = new Hilo.Stage({
        renderType:'canvas',
        container: gameContainer,
        width: 800,
        height: 600
    });

    var sum = 5000;
    var bitmaps = [];

   var ticker = new Hilo.Ticker();
   ticker.addTick(stage);
   ticker.start(true);

    for(var i = 0; i < sum; i++) {
            var bmp = new Hilo.Bitmap({
            image: 'images/hero.jpg',
            rect: [0, 0, 50, 50],
            x: Math.random()*800,
            y: Math.random()*600
            }).addTo(stage);
            bitmaps.push(bmp);
    }

    function animate() {
        requestAnimationFrame(animate);
        for(var i = 0; i < sum; i++) {
                    bitmaps[i].rotation += 0.1;
        }
    }
    animate();
}

テストの実行環境

image.png

テスト結果

image.png

結論

上記のテスト結果から、エンジンを性能順におおまかにランキングしてみました。

順位 エンジン
1位 Pixi.js と Turbulenz
2位 Egret
3位 Cocos2d-js
4位 Hilo
5位 enchant.js
6位 melonJS

描画テストのイメージ

image.png

訳者からの参考情報

TinyUnityのパフォーマンスを知りたい方はこちらの情報をご確認ください
Tiny Unity (Unity Tiny Mode)のベンチマークを書いてみた

ざっくりなイメージですが、TinyUnityはenchant.jsのようなレベルです。

これからコンパイラを作り替えることで速くなるという話ですが、これだけの差を詰めるのは相当難しいのではないでしょうか。またTypeScriptがC#になるということでオーバーヘッドも発生してよりリリース版ではさらに重くなる可能性もあります。

あとは、Adobe Animate CCのベンチマークにも期待したいところです。


つづく

HTML5ゲームエンジンの徹底評価日本語訳 ドキュメント編&実績編&最終結論

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2