- HTML5ゲームエンジンの徹底評価 [日本語訳] (1) 2D・3D対応とプログラミング言語の比較
- HTML5ゲームエンジンの徹底評価 [日本語訳] (2) Three.jsとPixi.js
- HTML5ゲームエンジンの徹底評価 [日本語訳] (3) PhaserとEgretEngine
- HTML5ゲームエンジンの徹底評価 [日本語訳] (4) enchant.jsとcrafty.js
- HTML5ゲームエンジンの徹底評価 [日本語訳] (5) Turbulenz, cocos2d-js, PlayCanvas, melonJS
- HTML5ゲームエンジンの徹底評価 [日本語訳] (6) 各エンジンのコンセプト・設計思想・機能比較のまとめ
パフォーマンス
ベンチマークのために、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();
}
テストの実行環境
テスト結果
結論
上記のテスト結果から、エンジンを性能順におおまかにランキングしてみました。
順位 | エンジン |
---|---|
1位 | Pixi.js と Turbulenz |
2位 | Egret |
3位 | Cocos2d-js |
4位 | Hilo |
5位 | enchant.js |
6位 | melonJS |
描画テストのイメージ
訳者からの参考情報
TinyUnityのパフォーマンスを知りたい方はこちらの情報をご確認ください
Tiny Unity (Unity Tiny Mode)のベンチマークを書いてみた
ざっくりなイメージですが、TinyUnityはenchant.jsのようなレベルです。
これからコンパイラを作り替えることで速くなるという話ですが、これだけの差を詰めるのは相当難しいのではないでしょうか。またTypeScriptがC#になるということでオーバーヘッドも発生してよりリリース版ではさらに重くなる可能性もあります。
あとは、Adobe Animate CCのベンチマークにも期待したいところです。
つづく