1 /**
  2  * @fileOverview
  3  * ui.enchant.js v2 (2012/11/05)
  4  * ui parts support
  5  * @require enchant.js v0.5.2 or later
  6  * @require image files for gamepad, icons (default: pad.png, apad.png, icon0.png, font0.png)
  7  *
  8  * @features
  9  * - D-Pad (left, right, up, down)
 10  * - Analog Pad
 11  * - Button (3 built-in themes and can be customized)
 12  * - MutableText
 13  * - ScoreLabel
 14  * - TimeLabel
 15  * - LifeLabel
 16  * - Bar
 17  * - VirtualMap
 18  *
 19  * @usage
 20  * [D-Pad]
 21  *      var pad = new Pad();
 22  *      pad.x = 0;
 23  *      pad.y = 220;
 24  *      core.rootScene.addChild(pad);
 25  *  (input of X direction can be detected from "Xbuttonup" "Xbuttondown" events
 26  *   or enchant.Core.instance.input.X)
 27  *
 28  * [A-Pad]
 29  *      var pad = new APad();
 30  *      pad.x = 0;
 31  *      pad.y = 220;
 32  *      core.rootScene.addChild(pad);
 33  *  (input can be detected from pad.vx/vy and pad.touched)
 34  *
 35  * [Button]
 36  *      var button = new Button("Press me");
 37  *      button.addEventListener("touchstart", function(){ ... })
 38  *      core.rootScene.addEventListener(button);
 39  *
 40  *      var button_light = new Button("Press me", "light");
 41  *      core.rootScene.addEventListener(button);
 42  *
 43  *      var button_blue = new Button("Press me", "blue");
 44  *      core.rootScene.addEventListener(button);
 45  */
 46 
 47 /**
 48  * @type {Object}
 49  */
 50 enchant.ui = { assets: ['pad.png', 'apad.png', 'icon0.png', 'font0.png'] };
 51 
 52 /**
 53  * 方向キーパッドのクラス: Pad
 54  * @scope enchant.ui.Pad
 55  */
 56 enchant.ui.Pad = enchant.Class.create(enchant.Sprite, {
 57     /**
 58      * 方向キーパッドオブジェクトを作成する。
 59      * @constructs
 60      * @extends enchant.Sprite
 61      */
 62     initialize: function() {
 63         var core = enchant.Core.instance;
 64         var image = core.assets['pad.png'];
 65         enchant.Sprite.call(this, image.width / 2, image.height);
 66         this.image = image;
 67         this.input = { left: false, right: false, up: false, down: false };
 68         this.addEventListener('touchstart', function(e) {
 69             this._updateInput(this._detectInput(e.localX, e.localY));
 70         });
 71         this.addEventListener('touchmove', function(e) {
 72             this._updateInput(this._detectInput(e.localX, e.localY));
 73         });
 74         this.addEventListener('touchend', function(e) {
 75             this._updateInput({ left: false, right: false, up: false, down: false });
 76         });
 77     },
 78     _detectInput: function(x, y) {
 79         x -= this.width * 0.5;
 80         y -= this.height * 0.5;
 81         var input = { left: false, right: false, up: false, down: false };
 82         if (x * x + y * y <= 2500 && x * x + y * y > 200) {
 83             if (x < 0 && y < x * x * 0.1 && y > x * x * -0.1) {
 84                 input.left = true;
 85             }
 86             if (x > 0 && y < x * x * 0.1 && y > x * x * -0.1) {
 87                 input.right = true;
 88             }
 89             if (y < 0 && x < y * y * 0.1 && x > y * y * -0.1) {
 90                 input.up = true;
 91             }
 92             if (y > 0 && x < y * y * 0.1 && x > y * y * -0.1) {
 93                 input.down = true;
 94             }
 95         }
 96         return input;
 97     },
 98     _updateInput: function(input) {
 99         var core = enchant.Core.instance;
100         ['left', 'right', 'up', 'down'].forEach(function(type) {
101             if (this.input[type] && !input[type]) {
102                 core.changeButtonState(type, false);
103             }
104             if (!this.input[type] && input[type]) {
105                 core.changeButtonState(type, true);
106             }
107         }, this);
108         this.input = input;
109     }
110 });
111 
112 /**
113  * アナログパッドのクラス: APad
114  * @scope enchant.ui.APad
115  */
116 enchant.ui.APad = enchant.Class.create(enchant.Group, {
117     /**
118      * アナログパッドオブジェクトを作成する。
119      * @constructs
120      * @extends enchant.Group
121      * @param mode
122      *   'direct': 入力ベクトルは正規化されない (大きさは 0~1 の間)
123      *   'normal': 入力ベクトルを常に正規化する (大きさは常に1となる)
124      */
125     initialize: function(mode) {
126         var core = enchant.Core.instance;
127         var image = core.assets['apad.png'];
128         var w = this.width = image.width;
129         var h = this.height = image.height;
130         enchant.Group.call(this);
131 
132         this.outside = new enchant.Sprite(w, h);
133         var outsideImage = new enchant.Surface(w, h);
134         outsideImage.draw(image, 0, 0, w, h / 4, 0, 0, w, h / 4);
135         outsideImage.draw(image, 0, h / 4 * 3, w, h / 4, 0, h / 4 * 3, w, h / 4);
136         outsideImage.draw(image, 0, h / 4, w / 4, h / 2, 0, h / 4, w / 4, h / 2);
137         outsideImage.draw(image, w / 4 * 3, h / 4, w / 4, h / 2, w / 4 * 3, h / 4, w / 4, h / 2);
138         this.outside.image = outsideImage;
139         this.inside = new enchant.Sprite(w / 2, h / 2);
140         var insideImage = new enchant.Surface(w / 2, h / 2);
141         insideImage.draw(image, w / 4, h / 4, w / 2, h / 2, 0, 0, w / 2, h / 2);
142         this.inside.image = insideImage;
143         this.r = w / 2;
144 
145         /**
146          * isTouched
147          * @type {Boolean}
148          * タッチされているかどうか
149          */
150         this.isTouched = false;
151 
152         /**
153          * vx, vy
154          * @type {Number}
155          * 入力ベクトルの(x, y)方向の大きさ
156          */
157         this.vx = 0;
158         this.vy = 0;
159 
160         /**
161          * rad, dist
162          * @type {Number}
163          * 入力ベクトルの極座標表示
164          * radは角度、distはベクトルの大きさを示す
165          */
166         this.rad = 0;
167         this.dist = 0;
168 
169         if (mode === 'direct') {
170             this.mode = 'direct';
171         } else {
172             this.mode = 'normal';
173         }
174         this._updateImage();
175         this.addChild(this.inside);
176         this.addChild(this.outside);
177         this.addEventListener('touchstart', function(e) {
178             this._detectInput(e.localX, e.localY);
179             this._calcPolar(e.localX, e.localY);
180             this._updateImage(e.localX, e.localY);
181             this._dispatchPadEvent('apadstart');
182             this.isTouched = true;
183         });
184         this.addEventListener('touchmove', function(e) {
185             this._detectInput(e.localX, e.localY);
186             this._calcPolar(e.localX, e.localY);
187             this._updateImage(e.localX, e.localY);
188             this._dispatchPadEvent('apadmove');
189         });
190         this.addEventListener('touchend', function(e) {
191             this._detectInput(this.width / 2, this.height / 2);
192             this._calcPolar(this.width / 2, this.height / 2);
193             this._updateImage(this.width / 2, this.height / 2);
194             this._dispatchPadEvent('apadend');
195             this.isTouched = false;
196         });
197     },
198     _dispatchPadEvent: function(type) {
199         var e = new enchant.Event(type);
200         e.vx = this.vx;
201         e.vy = this.vy;
202         e.rad = this.rad;
203         e.dist = this.dist;
204         this.dispatchEvent(e);
205     },
206     _updateImage: function(x, y) {
207         x -= this.width / 2;
208         y -= this.height / 2;
209         this.inside.x = this.vx * (this.r - 10) + 25;
210         this.inside.y = this.vy * (this.r - 10) + 25;
211     },
212     _detectInput: function(x, y) {
213         x -= this.width / 2;
214         y -= this.height / 2;
215         var distance = Math.sqrt(x * x + y * y);
216         var tan = y / x;
217         var rad = Math.atan(tan);
218         var dir = x / Math.abs(x);
219         if (distance === 0) {
220             this.vx = 0;
221             this.vy = 0;
222         } else if (x === 0) {
223             this.vx = 0;
224             if (this.mode === 'direct') {
225                 this.vy = (y / this.r);
226             } else {
227                 dir = y / Math.abs(y);
228                 this.vy = Math.pow((y / this.r), 2) * dir;
229             }
230         } else if (distance < this.r) {
231             if (this.mode === 'direct') {
232                 this.vx = (x / this.r);
233                 this.vy = (y / this.r);
234             } else {
235                 this.vx = Math.pow((distance / this.r), 2) * Math.cos(rad) * dir;
236                 this.vy = Math.pow((distance / this.r), 2) * Math.sin(rad) * dir;
237             }
238         } else {
239             this.vx = Math.cos(rad) * dir;
240             this.vy = Math.sin(rad) * dir;
241         }
242     },
243     _calcPolar: function(x, y) {
244         x -= this.width / 2;
245         y -= this.height / 2;
246         var add = 0;
247         var rad = 0;
248         var dist = Math.sqrt(x * x + y * y);
249         if (dist > this.r) {
250             dist = this.r;
251         }
252         dist /= this.r;
253         if (this.mode === 'normal') {
254             dist *= dist;
255         }
256         if (x >= 0 && y < 0) {
257             add = Math.PI / 2 * 3;
258             rad = x / y;
259         } else if (x < 0 && y <= 0) {
260             add = Math.PI;
261             rad = y / x;
262         } else if (x <= 0 && y > 0) {
263             add = Math.PI / 2;
264             rad = x / y;
265         } else if (x > 0 && y >= 0) {
266             add = 0;
267             rad = y / x;
268         }
269         if (x === 0 || y === 0) {
270             rad = 0;
271         }
272         this.rad = Math.abs(Math.atan(rad)) + add;
273         this.dist = dist;
274     }
275 });
276 
277 /**
278  * ボタンオブジェクトのクラス: Button
279  * available in only DOMGroup
280  *
281  * @scope enchant.ui.Button.prototype
282  * @deprecated
283  * @classes
284  */
285 enchant.ui.Button = enchant.Class.create(enchant.Entity, {
286     /**
287      * ボタンオブジェクトを作成する。
288      * @constructs
289      * @extends enchant.Entity
290      */
291     initialize: function(text, theme, height, width) {
292         enchant.Entity.call(this);
293 
294         if (enchant.CanvasLayer) {
295             this._element = 'div';
296         }
297 
298         this.width = width || null;
299         this.height = height || null;
300         this.text = text;
301         this.pressed = false;
302 
303         // デフォルトのスタイル (テーマで上書き可能)
304         var style = this._style;
305         style["display"] = "inline-block";
306         style["font-size"] = "12px";
307         style["height"] = "2em";
308         style["line-height"] = "2em";
309         style["min-width"] = "2em";
310         style["padding"] = "2px 10px";
311         style["text-align"] = "center";
312         style["font-weight"] = "bold";
313         style["border-radius"] = "0.5em";
314 
315         // テーマの指定がなければ "dark" を使う
316         theme = theme || "dark";
317 
318         if (typeof theme === "string") {
319             // theme 引数が string なら、その名前のデフォルトテーマを使う
320             this.theme = enchant.ui.Button.DEFAULT_THEME[theme];
321         } else {
322             // theme 引数が object なら、その引数をテーマとして扱う
323             this.theme = theme;
324         }
325 
326         // テーマを適用する
327         this._applyTheme(this.theme.normal);
328 
329         // タッチしたときの挙動
330         this.addEventListener("touchstart", function() {
331             this._applyTheme(this.theme.active);
332             this.pressed = true;
333             this.y++;
334         });
335 
336         // タッチが離されたときの挙動
337         this.addEventListener("touchend", function() {
338             this._applyTheme(this.theme.normal);
339             this.pressed = false;
340             this.y--;
341         });
342     },
343     _applyTheme: function(theme) {
344         var style = this._style;
345         var css = enchant.ui.Button.theme2css(theme);
346         for (var i in css) {
347             if (css.hasOwnProperty(i)) {
348                 style[i] = css[i];
349             }
350         }
351     },
352     /**
353      * 表示するテキスト
354      * @type {String}
355      */
356     text: {
357         get: function() {
358             return this._text;
359         },
360         set: function(text) {
361                 this._text = text;
362             if (!enchant.CanvasLayer) {
363                 this._element.innerHTML = text;
364             }
365         }
366     },
367     /**
368      * フォントサイズ
369      */
370     size: {
371         get: function() {
372             return this._style.fontSize;
373         },
374         set: function(size) {
375             this._style.fontSize = size;
376         }
377     },
378     /**
379      * フォントの指定
380      * @type {String}
381      */
382     font: {
383         get: function() {
384             return this._style.font;
385         },
386         set: function(font) {
387             this._style.font = font;
388         }
389     },
390     /**
391      * Text color settings.
392      * CSS 'color' can be set to same format as properties.
393      * @type {String}
394      */
395     color: {
396         get: function() {
397             return this._style.color;
398         },
399         set: function(color) {
400             this._style.color = color;
401         }
402     },
403     cvsRender: function() {
404         // not available now
405     },
406     domRender: function() {
407         var element = this._domManager.element;
408         element.innerHTML = this._text;
409         element.style.font = this._font;
410         element.style.color = this._color;
411         element.style.textAlign = this._textAlign;
412     }
413 });
414 
415 enchant.ui.Button.theme2css = function(theme) {
416     var prefix = '-' + enchant.ENV.VENDOR_PREFIX.toLowerCase() + '-';
417     var obj = {};
418     var bg = theme.background;
419     var bd = theme.border;
420     var ts = theme.textShadow;
421     var bs = theme.boxShadow;
422     if (prefix === '-ms-') {
423         obj['background'] = bg.start;
424     } else {
425         obj['background-image'] = prefix + bg.type + '('+ [ 'top', bg.start, bg.end ] + ')';
426     }
427     obj['color'] = theme.color;
428     obj['border'] = bd.color + ' ' + bd.width + ' ' + bd.type;
429     obj['text-shadow'] = ts.offsetX + 'px ' + ts.offsetY + 'px ' + ts.blur + ' ' + ts.color;
430     obj['box-shadow'] = bs.offsetX + 'px ' + bs.offsetY + 'px ' + bs.blur + ' ' + bs.color;
431     return obj;
432 };
433 
434 enchant.ui.Button.DEFAULT_THEME = {
435     dark: {
436         normal: {
437             color: '#fff',
438             background: { type: 'linear-gradient', start: '#666', end: '#333' },
439             border: { color: '#333', width: 1, type: 'solid' },
440             textShadow: { offsetX: 0, offsetY: 1, blur: 0, color: '#666' },
441             boxShadow: { offsetX: 0, offsetY: 1, blur: 0, color: 'rgba(255, 255, 255, 0.3)' }
442         },
443         active: {
444             color: '#ccc',
445             background: { type: 'linear-gradient', start: '#333', end: '#000' },
446             border: { color: '#333', width: 1, type: 'solid' },
447             textShadow: { offsetX: 0, offsetY: 1, blur: 0, color: '#000' },
448             boxShadow: { offsetX: 0, offsetY: 1, blur: 0, color: 'rgba(255, 255, 255, 0.3)' }
449         }
450     },
451     light: {
452         normal: {
453             color: '#333',
454             background: { type: 'linear-gradient', start: '#fff', end:'#ccc' },
455             border: { color: '#999', width: 1, type: 'solid' },
456             textShadow: { offsetX: 0, offsetY: 1, blur: 0, color: '#fff' },
457             boxShadow: { offsetX: 0, offsetY: 1, blur: 0, color: 'rgba(0, 0, 0, 1)' },
458         },
459         active: {
460             color: '#333',
461             background: { type: 'linear-gradient', start: '#ccc', end: '#999' },
462             border: { color: '#666', width: 1, type: 'solid' },
463             textShadow: { offsetX: 0, offsetY: 1, blur: 0, color: '#ccc' },
464             boxShadow: { offsetX: 0, offsetY: 1, blur: 0, color: 'rgba(255, 255, 255, 0.3)' }
465         }
466     },
467     blue: {
468         normal: {
469             color: '#fff',
470             background: { type: 'linear-gradient', start: '#04f', end: '#04c' },
471             border: { color: '#026', width: 1, type: 'solid' },
472             textShadow: { offsetX: 0, offsetY: 1, blur: 0, color: '#666' },
473             boxShadow: { offsetX: 0, offsetY: 1, blur: 0, color: 'rgba(0, 0, 0, 0.5)' }
474         },
475         active: {
476             color: '#ccc',
477             background: { type: 'linear-gradient', start: '#029', end: '#026' },
478             border: { color: '#026', width: 1, type: 'solid' },
479             textShadow: { offsetX: 0, offsetY: 1, blur: 0, color: '#000' },
480             boxShadow: 'none'
481         }
482     }
483 };
484 
485 /**
486  * @scope enchant.ui.MutableText.prototype
487  * @type {*}
488  */
489 enchant.ui.MutableText = enchant.Class.create(enchant.Sprite, {
490     /**
491      * ビットマップフォントを用いたラベルクラス
492      * (参考: draw.text.js http://d.hatena.ne.jp/nakamura001/20110430/1304181043)
493      * enchant.js 添付素材の font*.png が利用可能。
494      *
495      * @usage
496      *     var text = new MutableText(0, 0);
497      *     game.text = 'Hello, world!';
498      *     game.rootScene.addChild(text);
499      *
500      * @constructs
501      * @param posX
502      * @param posY
503      * @param width
504      */
505     initialize: function(x, y, width) {
506         enchant.Sprite.call(this, 0, 0);
507         this.fontSize = 16;
508         this.widthItemNum = 16;
509         this.x = x;
510         this.y = y;
511         this._imageAge = Number.MAX_VALUE;
512         this.text = '';
513         if (arguments[2]) {
514             this.row = Math.floor(arguments[2] / this.fontSize);
515         }
516     },
517     /**
518      * ラベルの内容を書き換える関数
519      * @param txt
520      */
521     setText: function(txt) {
522         var i, x, y, wNum, charCode, charPos;
523         this._text = txt;
524         var newWidth;
525         if (!this.returnLength) {
526             this.width = Math.min(this.fontSize * this._text.length, enchant.Game.instance.width);
527         } else {
528             this.width = Math.min(this.returnLength * this.fontSize, enchant.Game.instance.width);
529         }
530         this.height = this.fontSize * (Math.ceil(this._text.length / this.row) || 1);
531         // if image is to small or was to big for a long time create new image
532         if(!this.image || this.width > this.image.width || this.height > this.image.height || this._imageAge > 300) {
533             this.image = new enchant.Surface(this.width, this.height);
534             this._imageAge = 0;
535         } else if(this.width < this.image.width || this.height < this.image.height) {
536             this._imageAge++;
537         } else {
538             this._imageAge = 0;
539         }
540         this.image.context.clearRect(0, 0, this.width, this.height);
541         for (i = 0; i < txt.length; i++) {
542             charCode = txt.charCodeAt(i);
543             if (charCode >= 32 && charCode <= 127) {
544                 charPos = charCode - 32;
545             } else {
546                 charPos = 0;
547             }
548             x = charPos % this.widthItemNum;
549             y = (charPos / this.widthItemNum) | 0;
550             this.image.draw(enchant.Game.instance.assets['font0.png'],
551                 x * this.fontSize, y * this.fontSize, this.fontSize, this.fontSize,
552                 (i % this.row) * this.fontSize, ((i / this.row) | 0) * this.fontSize, this.fontSize, this.fontSize);
553         }
554     },
555     /**
556      * ラベルの内容
557      * @type {String}
558      */
559     text: {
560         get: function() {
561             return this._text;
562         },
563         set: function(txt) {
564             this.setText(txt);
565         }
566     },
567     /**
568      * @type {Number}
569      */
570     row: {
571         get: function() {
572             return this.returnLength || this.width / this.fontSize;
573         },
574         set: function(row) {
575             this.returnLength = row;
576             this.text = this.text;
577         }
578     }
579 });
580 
581 /**
582  * @scope enchant.ui.ScoreLabel.prototype
583  * @type {*}
584  */
585 enchant.ui.ScoreLabel = enchant.Class.create(enchant.ui.MutableText, {
586     /**
587      * スコアを表示するラベル。
588      * 画像フォントクラス (MutableText) を使って表示する。
589      * @constructs
590      * @param x
591      * @param y
592      */
593     initialize: function(x, y) {
594         enchant.ui.MutableText.call(this, 0, 0);
595         switch (arguments.length) {
596             case 2:
597                 this.y = y;
598                 this.x = x;
599                 break;
600             case 1:
601                 this.x = x;
602                 break;
603             default:
604                 break;
605         }
606         this._score = 0;
607         this._current = 0;
608         this.easing = 2.5;
609         this.text = this.label = 'SCORE:';
610         this.addEventListener('enterframe', function() {
611             if (this.easing === 0) {
612                 this.text = this.label + (this._current = this._score);
613             } else {
614                 var dist = this._score - this._current;
615                 if (0 < dist) {
616                     this._current += Math.ceil(dist / this.easing);
617                 } else if (dist < 0) {
618                     this._current += Math.floor(dist / this.easing);
619                 }
620                 this.text = this.label + this._current;
621             }
622         });
623     },
624     /**
625      * スコア
626      * @type {Number}
627      */
628     score: {
629         get: function() {
630             return this._score;
631         },
632         set: function(newscore) {
633             this._score = newscore;
634         }
635     }
636 });
637 
638 /**
639  * @type {*}
640  * @scope enchant.ui.TimeLabel.prototype
641  */
642 enchant.ui.TimeLabel = enchant.Class.create(enchant.ui.MutableText, {
643     /**
644      * 残り時間などのタイムを表示するラベル
645      * @constructs
646      * @param x
647      * @param y
648      * @param counttype
649      */
650     initialize: function(x, y, counttype) {
651         enchant.ui.MutableText.call(this, 0, 0);
652         switch (arguments.length) {
653             case 3:
654             case 2:
655                 this.y = y;
656                 this.x = x;
657                 break;
658             case 1:
659                 this.x = x;
660                 break;
661             default:
662                 break;
663         }
664         this._time = 0;
665         this._count = 1;// この数を毎フレーム每に足して上げ下げを制御する
666         if (counttype === 'countdown') {
667             this._count = -1;
668         }
669         this.text = this.label = 'TIME:';
670         this.addEventListener('enterframe', function() {
671             this._time += this._count;
672             this.text = this.label + (this._time / enchant.Game.instance.fps).toFixed(2);
673         });
674     },
675     /**
676      * 残り時間
677      * @type {Number}
678      */
679     time: {
680         get: function() {
681             return Math.floor(this._time / enchant.Game.instance.fps);
682         },
683         set: function(newtime) {
684             this._time = newtime * enchant.Game.instance.fps;
685         }
686     }
687 });
688 
689 /**
690  * @type {*}
691  * @scope enchant.ui.LifeLabel.prototype
692  */
693 enchant.ui.LifeLabel = enchant.Class.create(enchant.Group, {
694     /**
695      * ライフを表示する専用のラベル
696      * icon0.png 内のハートの画像を用いる
697      * @constructs
698      * @param x
699      * @param y
700      * @param maxlife
701      */
702     initialize: function(x, y, maxlife) {
703         enchant.Group.call(this);
704         this.x = x || 0;
705         this.y = y || 0;
706         this._maxlife = maxlife || 9;
707         this._life = 0;
708         this.label = new enchant.ui.MutableText(0, 0, 80);
709         this.label.text = 'LIFE:';
710         this.addChild(this.label);
711         this.heart = [];
712         for (var i = 0; i < this._maxlife; i++) {
713             this.heart[i] = new enchant.Sprite(16, 16);
714             this.heart[i].image = enchant.Game.instance.assets['icon0.png'];
715             this.heart[i].x = this.label.width + i * 16;
716             this.heart[i].y = -3;
717             this.heart[i].frame = 10;
718             this.addChild(this.heart[i]);
719         }
720     },
721     /**
722      * 残りライフの数
723      * @type {Number}
724      */
725     life: {
726         get: function() {
727             return this._life;
728         },
729         set: function(newlife) {
730             this._life = newlife;
731             if (this._maxlife < newlife) {
732                 this._life = this._maxlife;
733             }
734             for (var i = 0; i < this._maxlife; i++) {
735                 this.heart[i].visible = (i <= newlife - 1);
736             }
737         }
738     }
739 });
740 
741 /**
742  * @scope enchant.ui.Bar
743  * @type {*}
744  */
745 enchant.ui.Bar = enchant.Class.create(enchant.Sprite, {
746     /**
747      * イージング付きのバークラス
748      * @constructs
749      * @param x
750      * @param y
751      */
752     initialize: function(x, y) {
753         enchant.Sprite.call(this, 1, 16);
754         this.image = new enchant.Surface(1, 16);// Null用
755         this.image.context.fillColor = 'RGB(0, 0, 256)';
756         this.image.context.fillRect(0, 0, 1, 16);
757         this._direction = 'right';
758         this._origin = 0;
759         this._maxvalue = enchant.Game.instance.width;
760         this._lastvalue = 0;
761         this.value = 0;
762         this.easing = 5;
763         switch (arguments.length) {
764             case 2:
765                 this.y = y;
766                 this.x = x;
767                 this._origin = x;
768                 break;
769             case 1:
770                 this.x = x;
771                 this._origin = x;
772                 break;
773             default:
774                 break;
775         }
776         this.addEventListener('enterframe', function() {
777             if (this.value < 0) {
778                 this.value = 0;
779             }
780             this._lastvalue += (this.value - this._lastvalue) / this.easing;
781             if (Math.abs(this._lastvalue - this.value) < 1.3) {
782                 this._lastvalue = this.value;
783             }
784             this.width = (this._lastvalue) | 0;
785             if (this.width > this._maxvalue) {
786                 this.width = this._maxvalue;
787             }
788             if (this._direction === 'left') {
789                 this._x = this._origin - this.width;
790             } else {
791                 this._x = this._origin;
792             }
793             this._updateCoordinate();
794         });
795     },
796     /**
797      * バーの向き ('right' or 'left')
798      * @default 'right'
799      * @type {String}
800      */
801     direction: {
802         get: function() {
803             return this._direction;
804         },
805         set: function(newdirection) {
806             if (newdirection !== 'right' && newdirection !== 'left') {
807                 // ignore
808             } else {
809                 this._direction = newdirection;
810             }
811         }
812     },
813     /**
814      * x 座標
815      * @type {Number}
816      */
817     x: {
818         get: function() {
819             return this._origin;
820         },
821         set: function(x) {
822             this._x = x;
823             this._origin = x;
824             this._dirty = true;
825         }
826     },
827     /**
828      * @type {Number}
829      */
830     maxvalue: {
831         get: function() {
832             return this._maxvalue;
833         },
834         set: function(val) {
835             this._maxvalue = val;
836         }
837     }
838 });
839 
840 /**
841  * @scope enchant.ui.VirtualMap.prototype
842  */
843 enchant.ui.VirtualMap = enchant.Class.create(enchant.Group, {
844     /**
845      * マップライクな Group
846      * addChildで Sprite 等を追加すると、自動的に mx, my プロパティが追加され、
847      * VirtualMap内での座標で Sprite を操作できる
848      *
849      * 使い方
850      * //20 x 20 メッシュの縦横320ピクセルの盤を作り、その上に16 x 16の駒を8つ並べる
851      * var board = new VirtualMap(20, 20);
852      * board.width = 320;
853      * board.height = 320;
854      * for(var i=0; i<8; i++){
855      *     var piece = new Sprite(16, 16);
856      *     piece.image = game.assets['icon0.gif'];
857      *     board.addChild(piece);
858      *     piece.mx = i + 3;
859      *     piece.my = 16;
860      * }
861      * game.rootScene.addChild(board);
862      *
863      * @param meshWidth
864      * @param meshHeight
865      * @constructs
866      */
867     initialize: function(meshWidth, meshHeight) {
868         enchant.Group.call(this);
869         this.meshWidth = meshWidth || 16;
870         this.meshHeight = meshHeight || 16;
871     },
872     /**
873      * VirtualMap にオブジェクトを追加する (自動的にバインドされる)
874      * @param obj
875      */
876     addChild: function(obj) {
877         enchant.Group.prototype.addChild.call(this, obj);
878         this.bind(obj);
879     },
880     /**
881      * VirtualMap にオブジェクトを追加する
882      * reference で指定したオブジェクトより前に追加される (自動的にバインドされる)。
883      * @param obj
884      * @param reference
885      */
886     insertBefore: function(obj, reference) {
887         enchant.Group.prototype.insertBefore.call(this, obj, reference);
888         this.bind(obj);
889     },
890     /**
891      * オブジェクトを VirtualMap にバインドする。
892      * バインドされたオブジェクトはメッシュ座標 mx, my プロパティを持ち、これを操作することで
893      * VirtualMap の中を移動させることができる。
894      * @param obj
895      */
896     bind: function(obj) {
897         Object.defineProperties(obj, {
898             "mx": {
899                 get: function() {
900                     return Math.floor(this.x / this.parentNode.meshWidth);
901                 },
902                 set: function(arg) {
903                     this.x = Math.floor(arg * this.parentNode.meshWidth);
904                 }
905             },
906             "my": {
907                 get: function() {
908                     return Math.floor(this.y / this.parentNode.meshHeight);
909                 },
910                 set: function(arg) {
911                     this.y = Math.floor(arg * this.parentNode.meshWidth);
912                 }
913             }
914         });
915         obj.mx = 0;
916         obj.my = 0;
917     }
918 });
919 
920 function rand(num) {
921     return Math.floor(Math.random() * num);
922 }
923