剣の軌跡を表示しよう
DeNAがスマートフォン向けに提供しているゲームに「忍者ロワイヤル」というものがあります。「忍者ロワイヤル」はスマートフォンならではの操作がウリのアクションRPGで、特にバトルはスワイプやタップを駆使して遊ぶものとなっています。今回はそのバトルの中の一要素である“剣の軌跡”の表示を実装します。今まで説明してきたタッチの仕組みに加え、GL2.Primitive Core.UpdateEmitterというクラスを使います。少し複雑になりますが、1つずつ見ていきましょう。
LIST5のソースコードを見てみましょう。
var GL2 = require('../NGCore/Client/GL2').GL2;
var UI = require('../NGCore/Client/UI').UI;
var Device = require('../NGCore/Client/Device').Device;
var Util = require('./lib/Util').Util;
var Trajectory = require('./entity/Trajectory').Trajectory;
//----- 初期化処理
function initialize() {
//----- 画面の向きを横に
Device.OrientationEmitter.setInterfaceOrientation(
Device.OrientationEmitter.Orientation.LandscapeLeft);
//-----
var width = UI.Window.getWidth();
var height = UI.Window.getHeight();
//-----
var bg = Util.makePrimitive(0, 0, height, width, [0.0, 0.0, 0.0]);
GL2.Root.addChild(bg);
//-----
var target = new GL2.TouchTarget();
target.setSize([height, width]);
GL2.Root.addChild(target);
//----- 剣の軌跡
var trajectory = new Trajectory(target.getTouchEmitter());
bg.addChild( this._trajectory._node );
}
//----- 起動時に呼ばれる
function main() {
new UI.GLView({
frame: [0, 0, UI.Window.getWidth(), UI.Window.getHeight()],
onLoad: initialize
}).setActive(true);
}
mainは今までと同じ内容ですね。initializeの中を見ていきましょう。
//----- 画面の向きを横に Device.OrientationEmitter.setInterfaceOrientation( Device.OrientationEmitter.Orientation.LandscapeLeft);
新しいクラスDevice.OrientationEmitterが出てきました。Device.OrientationEmitterは端末の向きの変化を通知したり、端末の向きを設定するためのクラスです。ここではsetInterfaceOrientationメソッドを呼び出し、横向きの描画を行うように設定しています。これで横長の画面でアプリが動くことになります。
//----- 剣の軌跡 var trajectory = new Trajectory(target.getTouchEmitter()); bg.addChild( this._trajectory._node );
次に出てきた新しいクラスはTrajectoryです。これは「忍者ロワイヤル」で切り出した“剣の軌跡を表示する”ためのクラスです。ここではコンストラクタにTouchEmitterを引き渡しています。
Trajectoryクラスの中身は、LIST6のようになります。今まで見てきたものより複雑なコードですが、1つずつ見ていきましょう。
var Core = require('../../NGCore/Client/Core').Core;
var GL2 = require('../../NGCore/Client/GL2').GL2;
var utils = require('../../DnLib/Dn/utils').utils;
exports.Trajectory = Core.MessageListener.subclass({
initialize: function(touchEmitter, length, colorHead, colorTail) {
//-----
this._emitter = touchEmitter;
this._maxLength = length || 20;
this._colorHead = colorHead || [1, 1, 0];
this._colorTail = colorTail || [1, 0, 0];
this._bladePower = 1.0; // 1.0 が MAX. 刀を折り返すたびに威力が減って軌跡が細くなる
this._vertexList = [];
this._isMoving = false;
//-----
this._emitter.addListener( this, this.onTouch );
//-----
this._node = new GL2.Node();
//-----
var p = new GL2.Primitive();
p.setType( GL2.Primitive.Type.TriangleStrip );
for (var i=0; i < this._maxLength * 2; i++) {
var v = new GL2.Primitive.Vertex([0, 0], [0, 0], [1, 0, 0]);
p.pushVertex(v);
}
this._prim = p;
this._node.addChild( this._prim );
this._node.setDepth( 1 );
//-----
Core.UpdateEmitter.addListener( this, this.onUpdate );
},
//--------------------------------------------------------------------------
destroy: function() {
Core.UpdateEmitter.removeListener(this);
this._emitter.removeListener(this);
this._prim.destroy();
},
//-------------------------------------------------------------------------
getVertexList: function() {
return this._vertexList;
},
getNode: function() {
return this._node;
},
//--------------------------------------------------------------------------
onUpdate: function( delta ) {
var v = this._vertexList;
var p = this._prim;
if (v.length >= 2) {
p.setVisible( true );
var bladeWidth = 10 * this._bladePower;
for (var i=0; i < v.length - 1; i++) {
var grad = 1 - (i / v.length);
var r = (this._colorHead[0] * grad) + (this._colorTail[0] * (1-grad));
var g = (this._colorHead[1] * grad) + (this._colorTail[1] * (1-grad));
var b = (this._colorHead[2] * grad) + (this._colorTail[2] * (1-grad));
var w = (i < 4) ? (i * 0.22) : g;
var bw = w * bladeWidth;
var vx = v[i ].x;
var vpx = v[i+1].x;
var vy = v[i ].y;
var vpy = v[i+1].y;
//----- 進行方向と直角に軌跡の太さの方向を算出
if (! v[i].dx) {
var theta = Math.atan2( vy - vpy, vx - vpx ) * 180 / Math.PI;
v[i].dx = utils.cos( theta + 90 );
v[i].dy = utils.sin( theta + 90 );
}
p.setVertex( i*2+0, new GL2.Primitive.Vertex(
[v[i].x - v[i].dx * bw, v[i].y - v[i].dy * bw], [0, 0], [r, g, b]));
p.setVertex( i*2+1, new GL2.Primitive.Vertex(
[v[i].x + v[i].dx * bw, v[i].y + v[i].dy * bw], [0, 0], [r, g, b]));
}
//----- 余った頂点は末尾にまとめる
var lastVertex = new GL2.Primitive.Vertex(
[v[ v.length - 1 ].x, v[ v.length - 1 ].y], [0, 0], [0, 0, 0]);
for (var i = v.length - 1; i < this._maxLength; i++) {
p.setVertex( i*2+0, lastVertex );
p.setVertex( i*2+1, lastVertex );
}
} else {
p.setVisible(false);
}
if (v.length > 0 && !this._isMoving) {
v.pop();
} else {
this._isMoving = false;
}
},
//--------------------------------------------------------------------------
onTouch: function( touch ) {
var x = touch.getPosition().getX();
var y = touch.getPosition().getY();
var vList = this._vertexList;
this._isMoving = false;
switch (touch.getAction()) {
case touch.Action.Start:
break;
case touch.Action.End:
break;
case touch.Action.Move:
vList.unshift( {x:x, y:y} );
this._isMoving = true;
break;
default:
break;
}
if (vList.length > this._maxLength) {
vList.pop();
}
return touch.getAction() === touch.Action.Start;
}
});

