Skin:
[NORMAL]
[BLUE] [DOS] [LIGHT]  / コピーするための表示 / 実行
このファイル: /home/web6047/www/cgi-bin/prj/20200515-RPG/基本的/20200620-ウィンドウ入力/20200703-新しいメモ/simple8.html
1 <html><!--ESCAPEPROCESS-->
2 <head>
3 <meta content="text/html; charset=UTF-8" http-equiv="content-type">
4 <script>console.clear();</script>
5 <script>
6 function onloadx() {
7 app = new App();
8 }
9
10 //---[C] App
11
12 class App {
13 loadImage( src, option ) {
14 let image = new Image();
15 image.src = src;
16 return image;
17 }
18 constructor() {
19 this.classname = "app";
20
21 let app = this;
22
23 this.cc = document.getElementById( "test" ).getContext( "2d" );
24 this.cc.font = "16px monospace";
25
26 this.bcc = document.createElement( "canvas" ).getContext( "2d" );
27 this.bcc.canvas.width = this.cc.canvas.width;
28 this.bcc.canvas.height = this.cc.canvas.height;
29
30 this.images = new Object();
31 this.images.aozora = this.loadImage( "_imgs/aozora.png" );
32 this.images.face1 = this.loadImage( "_imgs/face1.png" );
33 this.images.monster1 = this.loadImage( "_imgs/monster1.png" );
34 this.images.monster2 = this.loadImage( "_imgs/monster2.png" );
35
36 this.chars = new Array();
37 //this.catalog内でthis.charsを参照しているのでここに書く
38
39 //---カタログ
40
41 this.catalog = {
42 batcom : {
43 "たたかう" : {
44 name : "たたかう",
45 },
46 "にげる" : {
47 name : "にげる",
48 },
49 "じゅもん" : function( char ) {
50 return {
51 name : "じゅもん",
52 menus : [
53 {
54 title : "",
55 items : char[ "じゅもん" ],
56 },
57 ],
58 };
59 },
60 "どうぐ" : function( char ) {
61 return {
62 name : "どうぐ",
63 menus : [ //※B
64 {
65 title : "",
66 items : char[ "どうぐ" ],
67 },
68 ],
69 };
70 },
71 "ぼうぎょ" : {
72 name : "ぼうぎょ",
73 },
74 },
75 //catalog
76 dousuru : {
77 "つかう" : {
78 name : "つかう",
79 },
80 "わたす" : {
81 name : "わたす",
82 },
83 "すてる" : {
84 name : "すてる",
85 },
86 },
87 //catalog
88 dougu : {
89 "やくそう" : {
90 name : "やくそう",
91 menus : [
92 {
93 title : "だれに",
94 items : this.chars,
95 },
96 ],
97 },
98 "dougu1" : {
99 name : "dougu1",
100 },
101 "dougu2" : {
102 name : "dougu2",
103 },
104 "dougu3" : {
105 name : "dougu3",
106 },
107 "dougu4" : {
108 name : "dougu4",
109 },
110 "dougu5" : {
111 name : "dougu5",
112 },
113 "dougu6" : {
114 name : "dougu6",
115 },
116 "dougu7" : {
117 name : "dougu7",
118 },
119 "dougu8" : {
120 name : "dougu8",
121 },
122 "dougu9" : {
123 name : "dougu9",
124 },
125 },
126 //catalog
127 jumon : {
128 "jumon1" : {
129 name : "jumon1",
130 menus : [
131 {
132 title : "だれに",
133 items : this.chars,
134 },
135 ],
136 },
137 "jumon2" : {
138 name : "jumon2",
139 },
140 "jumon3" : {
141 name : "jumon3",
142 },
143 "jumon4" : {
144 name : "jumon4",
145 },
146 "jumon5" : {
147 name : "jumon5",
148 },
149 "jumon6" : {
150 name : "jumon6",
151 },
152 },
153 }//catalog
154
155 //---キャラ
156
157 this.chars[ 0 ] = {
158 name : "C1",
159 HP : 123,
160 "どうぐ" : [
161 this.catalog.dougu[ "やくそう" ],
162 this.catalog.dougu[ "dougu1" ],
163 this.catalog.dougu[ "dougu2" ],
164 ],
165 };
166 this.chars[ 0 ].batcoms = [
167 this.catalog.batcom[ "たたかう" ],
168 this.catalog.batcom[ "にげる" ],
169 this.catalog.batcom[ "どうぐ" ]( this.chars[ 0 ] ),
170 this.catalog.batcom[ "ぼうぎょ" ],
171 ];
172
173 this.chars[ 1 ] = {
174 name : "C2",
175 HP : 203,
176 "どうぐ" : [
177 this.catalog.dougu[ "dougu3" ],
178 this.catalog.dougu[ "dougu4" ],
179 this.catalog.dougu[ "dougu5" ],
180 ],
181 "じゅもん" : [
182 this.catalog.jumon[ "jumon1" ],
183 this.catalog.jumon[ "jumon2" ],
184 this.catalog.jumon[ "jumon3" ],
185 ],
186 };
187 this.chars[ 1 ].batcoms = [
188 this.catalog.batcom[ "たたかう" ],
189 this.catalog.batcom[ "じゅもん" ]( this.chars[ 1 ] ),
190 this.catalog.batcom[ "どうぐ" ]( this.chars[ 1 ] ),
191 this.catalog.batcom[ "ぼうぎょ" ],
192 ];
193
194 this.chars[ 2 ] = {
195 name : "C3",
196 HP : 100,
197 "どうぐ" : [
198 this.catalog.dougu[ "dougu6" ],
199 this.catalog.dougu[ "dougu7" ],
200 this.catalog.dougu[ "dougu8" ],
201 ],
202 "じゅもん" : [
203 this.catalog.jumon[ "jumon4" ],
204 this.catalog.jumon[ "jumon5" ],
205 this.catalog.jumon[ "jumon6" ],
206 ],
207 };
208 this.chars[ 2 ].batcoms = [
209 this.catalog.batcom[ "たたかう" ],
210 this.catalog.batcom[ "じゅもん" ]( this.chars[ 2 ] ),
211 this.catalog.batcom[ "どうぐ" ]( this.chars[ 2 ] ),
212 this.catalog.batcom[ "ぼうぎょ" ],
213 ];
214
215 //---campMenu
216
217 this.campMenu = {
218 title : "どうする?",
219 items : [
220 { name : "はなす" },
221 { name : "つよさ",
222 menus : [
223 {
224 title : "だれの",
225 items : this.chars,
226 oncursormove : function() {
227 let win = app.getDrawingElementById( "つよさ表示" );
228 //check.
229 if( ! win ) {
230 let cx = local.parentMenu.cx + local.parentMenu.cw + 1;
231 let cy = local.parentMenu.cy - 1;
232 win = new Win( "つよさ表示", cx,cy,10,10, this );
233 win.padding = 16;
234 app.addDrawingElement( win );
235 }
236 let char = local.selectedItem;
237 win.contents.length = 0;
238 win.contents.push( { type : "str", data : char.name } );
239 win.contents.push( { type : "ctr", data : "ret" } );
240 win.contents.push( { type : "str", data : "HP:" } );
241 win.contents.push( { type : "obj", object : char, member : "HP" } );
242 win.contents.push( { type : "ctr", data : "ret" } );
243 },
244 },
245 {
246 title : "",
247 items : null, //※A
248 },
249 ],
250 },
251 { name : "そうび" },
252 { name : "どうぐ",
253 menus : [ //※B
254 {
255 title : "だれの",
256 items : this.chars,
257 onselect : function() {
258 //下記※A部をこのメニューの選択結果で決める
259 let char = local.selectedItem;
260 local.nextMenu.items = char[ "どうぐ" ];
261 },
262 },
263 {
264 title : "",
265 items : null, //※A
266 },
267 //選んだどうぐにmenusがあれば、この後メニューがつづく(やくそう、だれに等)
268 ],
269 },
270 { name : "じゅもん" },
271 { name : "しらべる" },
272 ],
273 onselect : null,
274 };//campMenu
275
276 this.anms = new Array();
277
278 this.timerMs = 100;
279 this.timerId = setInterval( this.frame.bind( this ), this.timerMs );
280 this.drawingElements = this.de = new Array();
281 this.intervalElements = new Array();
282
283 // this.battleEvent();
284 this.campEvent();
285 // this.test();
286
287 }//constructor
288
289 async test() {
290 let win, serif;
291 switch( 6 ) {
292 case 1: //メッセージを表示
293 win = new Win( "test", 10,10,10,10, this );
294 win.contents.push( { type : "str", data : "Hello" } );
295 win.contents.push( { type : "ctr", data : "ret" } );
296 win.contents.push( { type : "str", data : "World!" } );
297 this.addDrawingElement( win );
298 break;
299 case 2: //値を表示
300 win = new Win( "test", 10,10,10,10, this );
301 win.contents.push( { type : "obj", object : this.chars[ 0 ], member : "HP" } );
302 this.addDrawingElement( win );
303 break;
304 case 3: //画像を表示
305 win = new Win( "test", 10,10,10,10, this );
306 win.contents.push( { type : "img", data : this.images.face1, width : "100%", height : "100%" } );
307 this.addDrawingElement( win );
308 break;
309 case 4: //セリフを表示
310 serif = new Serif( "test", 5, 20, 20, 5, 0.5, this );
311 this.addDrawingElement( serif );
312 this.addIntervalElement( serif );
313 await serif.print( "あいうえお" );
314 await serif.print( "かきくけこ\n" );
315 for( let i = 0; i < 20; i++ ) await serif.print( "さしすせそ" );
316 await serif.print( "たちつてと\n" );
317 break;
318 case 5: //CtrlWinの例 hitanykey
319 win = new CtrlWin( "test", 3, 3, 10, 5 );
320 win.contents.push( { type : "str", data : "Hit Any Key" } );
321 this.addDrawingElement( win );
322 await win.hitanykey();
323 win.visibility = false;
324 break;
325 case 6: //CtrlWinの例 keytype
326 win = new CtrlWin( "test", 3, 3, 10, 5 );
327 win.contents.push( { type : "str", data : "see console" } );
328 this.addDrawingElement( win );
329 win.keytype = function( key ) {
330 switch( key ) {
331 case 32:
332 console.log( 123 );
333 break;
334 default:
335 console.log( key );
336 }
337 }
338 win.activate();
339 break;
340
341 }
342
343 }
344
345 //---App.battleEvent
346
347 async battleEvent() {
348
349 //フリップ
350 let monsterWin = new Win( "monsterWin", "center", 5, 17, 13, this );
351 monsterWin.contents.push( {
352 type : "img",
353 data : this.images.aozora,
354 width : 17 * 16,
355 height : 13 * 16,
356 } );
357 /* let z = 0.48;
358 monsterWin.contents.push( {
359 type : "img",
360 data : this.images.monster2,
361 position : "absolute",
362 align : "center",
363 bottom : 0,
364 width : 640 * z,
365 height : 480 * z,
366 } );*/
367 let z = 0.4;
368 monsterWin.contents.push( {
369 type : "img",
370 data : this.images.monster2,
371 position : "absolute",
372 align : "center",
373 bottom : 0,
374 width : 640 * z,
375 height : 480 * z,
376 } );
377 this.addDrawingElement( monsterWin );
378
379 //フリップ
380 let statusWin = new Win( "statusWin", 1, 1, 7, 10, this );
381 statusWin.contents.push( {
382 type : "img",
383 data : this.images.face1,
384 align : "center",
385 width : 48,
386 height : 48,
387 } );
388 statusWin.contents.push( { type : "ctr", data : "ret" } );
389 statusWin.contents.push( { type : "str", data : "HP:" } );
390 statusWin.contents.push( {
391 type : "obj",
392 object : this.chars[ 0 ],
393 member : "HP",
394 position : "absolute",
395 right : 0,
396 } );
397 statusWin.padding = 16;
398 statusWin.relativeY = -24;
399 this.addDrawingElement( statusWin );
400
401 //セリフ
402 let serif = new Serif( "serif", 6, 17, 20, 10, 0.5, this );
403 this.addDrawingElement( serif );
404 this.addIntervalElement( serif );
405 serif.visibility = false;
406
407 //ターンループ
408 while( 1 ) {
409 //コマンド入力
410 let results = new Array();
411 for( let i = 0; i < this.chars.length; i++ ) {
412 let char = this.chars[ i ];
413 let menu = {
414 title : char.name,
415 items : char.batcoms,
416 }
417
418 let result = await this.selectMenu( [ menu ], 1, 17, "select" );
419
420 //check. キャンセル操作された
421 if( ! result ) {
422 if( i == 0 ) {
423 i = -1;
424 } else {
425 i -= 2;
426 results.pop();
427 }
428 continue;
429 }
430
431 results.push( result );
432 }
433
434 //debug.
435 serif.visibility = true;
436 for( let i = 0; i < results.length; i++ ) {
437 let result = results[ i ];
438 await serif.print( this.chars[ i ].name + result.join( ", " ) + "\n" );
439 await serif.hitanykey();
440 }
441
442 //ターン実行
443 this.chars[ 0 ].HP -= 5;
444
445
446 }//while 1
447 }//battleEvent
448
449 //---App.campEvent
450
451 async campEvent() {
452 console.log( 123 );
453 let res = await this.selectMenu( [ this.campMenu ], 2, 2, "execute" );
454 if( res[ 0 ] == "しらべる" ) this.battleEvent();
455 }
456
457 //---App.selectMenu
458
459 selectMenu( menus, cx, cy, mode ) {
460 /*
461 menus が配列である理由:
462 たとえば、
463 campMenu>どうぐ>だれの? を決めたとき、次のメニューを指定して、そのキャラのどうぐを項目として
464 設定したいから。
465 その具体例は、
466 campMenu>どうぐのmenus
467 */
468 return new Promise(
469 async function( tellOk ) {
470
471 let menusSeek = 0;
472 let menuObject;
473 let hist = new Array();
474
475 while( 1 ) {
476
477 if( ! menuObject ) {
478 //subMenu遷移
479 let menu = menus[ menusSeek ++ ];
480
481 menuObject = new MenuObject( "menu", menu, cx, cy, null, null );
482 this.addDrawingElement( menuObject );
483 hist.push( {
484 menus : menus,
485 menusSeek : menusSeek,
486 menuObject : menuObject,
487 } );
488 }
489
490 //メニュー入力待ち
491 let selectedItem = await this.selectMenuSingle( menuObject );
492
493 //check.1 キャンセル
494 if( ! selectedItem ) {
495 //check. トップメニューをキャンセル
496 if( hist.length == 1 ) {
497 tellOk( null );
498 break;
499 }
500 //各種情報を復元
501 this.removeDrawingElement( menuObject );
502 hist.pop();
503 let before = hist[ hist.length - 1 ];
504 menus = before.menus;
505 menusSeek = before.menusSeek;
506 menuObject = before.menuObject;
507 cx -= menuObject.cw;
508 continue;
509 }
510
511
512 //check.2 onselect
513 //onselect実行により、「選択後、次のメニューの項目を書き換える」といったことが可能になる。
514 if( menuObject.onselect ) {
515 //---onselect 実行場所
516 //この{..}ブロックをスコープにしてonselectの内容を実行する
517 //使用可能変数群
518 let local = {
519 selectedItem : selectedItem,
520 nextMenu : menus[ menusSeek ],
521 }
522 //関数をここで定義し直すと関数の中でこのブロックの変数が使える。
523 let onselect_here;
524 eval( "onselect_here = " + menuObject.onselect.toString() );
525 onselect_here();
526 }
527
528
529 //check.3 サブメニュー
530 //選択した項目がmenusを持つなら、サブメニューを開始する。
531 //(注:それまでのmenusは途中でも破棄される)
532 if( selectedItem.menus ) {
533 menus = selectedItem.menus;
534 menusSeek = 0;
535 }
536
537
538 //check.4 決定
539 if(
540 ! menus //トップメニューにてサブメニューのない項目を選んだ場合は「決定」、
541 || menusSeek == menus.length //または、サブメニューの末端に至った場合も「決定」
542 ) {
543 let result = hist.map( obj => obj.menuObject.items[ obj.menuObject.cursorY ].name );
544 if( mode == "execute" ) {
545 //コマンド実行
546 console.log( "実行", result.join( ", " ) );
547 if( result[ 0 ] == "しらべる" ) {
548 tellOk( result );
549 break;
550 }
551 continue; //campだから繰り返す
552 } else if( mode == "select" ) {
553 tellOk( result );
554 break;
555 }
556 }//if
557
558 //次のサブメニュー作成のために
559 cx += menuObject.cw;
560 menuObject = null;
561
562 }//while 1
563 //全メニューを閉じる
564 hist.map( menuObject => this.removeDrawingElement( menuObject ) );
565
566 }.bind( this ) //function
567 );//promise
568
569
570 }//campEvent
571
572 //---App.selectMenuSingle
573
574 selectMenuSingle( menuObject ) {
575 return new Promise(
576 function( tellOk ) {
577 menuObject.activate( tellOk );
578 }.bind( this )
579 );
580 }
581
582 //---App.addIntervalElement
583
584 addIntervalElement( object ) {
585 this.intervalElements.push( object );
586 }
587
588 //---App.removeIntervalElement
589
590 removeIntervalElement( object ) {
591 this.intervalElements.splice( this.drawingElements.indexOf( object ), 1 );
592 }
593
594 //---App.addDrawingElement
595
596 addDrawingElement( object ) {
597 this.drawingElements.push( object );
598 }
599
600 //---App.removeDrawingElement
601
602 removeDrawingElement( object ) {
603 this.drawingElements.splice( this.drawingElements.indexOf( object ), 1 );
604 }
605
606 //---App.getDrawingElementById
607
608 getDrawingElementById( id ) {
609 for( let i = 0; i < this.drawingElements.length; i++ ) {
610 let element = this.drawingElements[ i ];
611 if( element.id == id ) return element;
612 }
613 }
614
615 //---App.frame
616
617 frame() {
618 //anms
619 for( let i = 0; i < this.anms.length; i++ ) {
620 let anm = this.anms[ i ];
621 anm.now += this.timerMs;
622 //check. 
623 if( anm.now < anm.timer ) continue;
624
625 anm.now = 0;
626
627 //check. 終了?
628 if( anm.frame() == false ) {
629 anm.callback();
630 this.anms.splice( i, 1 );
631 i--;
632 }
633 }
634 //interval
635 for( let i = 0; i < this.intervalElements.length; i++ ) {
636 let object = this.intervalElements[ i ];
637 object.frame.call( object );
638 }
639
640 this.draw( this.cc );
641 }
642
643 //---App.draw
644
645 draw( cc ) {
646 cc.clearRect( 0, 0, cc.canvas.width, cc.canvas.height );
647 for( let i = 0; i < this.drawingElements.length; i++ ) {
648 this.drawingElements[ i ].draw( this.cc );
649 }
650 }
651
652 }//App
653
654 //---classes
655 /*
656 Win 情報掲示
657 Win-> CtrlWin
658 CtrlWin-> MenuObject メニュー
659 CtrlWin-> Serif セリフ表示
660
661 */
662
663 //---[C] Anm
664
665 class Anm {
666 constructor() {
667 this.classname = "Anm";
668 this.timer = 0;
669 this.now = 0;
670 this.frame = null;
671 this.callback = null;
672 }
673 }
674
675 //---[C] Win
676
677 class Win {
678 constructor( id, cx, cy, cw, ch ) {
679 this.classname = "Win";
680 this.id = id;
681 this.cx = cx;
682 this.cy = cy;
683 this.cw = cw;
684 this.ch = ch;
685 this.visibility = true;
686 this.contents = new Array();
687 this.padding = 0;
688 this.relativeY = 0; //画像がウィンドウをはみ出す「断ち切り」を行いたいときに使用する値
689 }
690
691 //---[M] Win.draw
692
693 draw( cc ) {
694
695 //check.
696 if( ! this.visibility ) return;
697
698 //枠
699 let gw = this.cw * 16;
700 let gh = this.ch * 16;
701 let gx;
702 //センタリング指定
703 if( this.cx == "center" ) {
704 gx = ( cc.canvas.width - gw ) / 2;
705 } else {
706 gx = this.cx * 16;
707 }
708 let gy = this.cy * 16;
709
710 //枠を描画(gw,ghは枠の太さを含まない)
711 cc.fillStyle = "white";
712 cc.fillRect( gx, gy, gw, gh );
713 cc.strokeStyle = "black";
714 cc.strokeRect( gx - 1, gy - 1, gw + 2, gh + 2 );
715
716 //内容物を描画
717 cc.save();
718 cc.translate( gx + this.padding, gy + this.padding + this.relativeY );
719 //パディングされた範囲のサイズ、その中での座標
720 let gw2 = gw - this.padding * 2;
721 let gh2 = gh - this.padding * 2;
722 let gx2 = 0;
723 let gy2 = 0;
724 let heights = new Array(); //改行時に必要な情報
725 for( let i = 0; i < this.contents.length; i++ ) {
726 let content = this.contents[ i ];
727 //座標やサイズ決め
728 let width, height;
729 switch( content.type ) {
730 case "img":
731 width = content.data.width;
732 height = content.data.height;
733 break;
734 case "str":
735 width = cc.measureText( content.data ).width;
736 height = 16;
737 break;
738 case "obj":
739 content.data = content.object[ content.member ];
740 width = cc.measureText( content.data ).width;
741 height = 16;
742 break;
743 case "ctr":
744 if( content.data == "ret" ) { //改行
745 gx2 = 0;
746 gy2 += Math.max( ...heights );
747 heights.length = 0;
748 continue;
749 }
750 break;
751 }
752 if( content.width ) {
753 let idx = String( content.width ).indexOf( "%" );
754 width = idx == -1 ? content.width : gw2 * content.width.substr( 0, idx ) / 100;
755 }
756 if( content.height ) {
757 let idx = String( content.height ).indexOf( "%" );
758 height = idx == -1 ? content.height : gh2 * content.height.substr( 0, idx ) / 100;
759 }
760 if( content.position && content.position == "absolute" ) {
761 if( typeof content.left !== "undefined" ) gx2 = content.left;
762 if( typeof content.right !== "undefined" ) gx2 = gw2 - width - content.right;
763 if( typeof content.top !== "undefined" ) gy2 = content.top;
764 if( typeof content.bottom !== "undefined" ) gy2 = gh2 - height - content.bottom;
765 } else {
766 heights.push( height );
767 }
768 if( content.align ) {
769 if( content.align == "center" ) {
770 gx2 = ( gw2 - width ) / 2;
771 }
772 }
773
774 //描画
775 switch( content.type ) {
776 case "img":
777 cc.drawImage( content.data, gx2, gy2, width, height );
778 break;
779 case "str":
780 case "obj":
781 cc.fillStyle = "black";
782 cc.fillText( content.data, gx2, gy2 + 16, width );
783 break;
784 }
785 gx2 += width;
786
787 }//for
788 cc.restore();
789 }
790 }//Win
791
792 //---[C] CtrlWin
793
794 class CtrlWin extends Win {
795 constructor( id, cx, cy, cw, ch ) {
796 super( id, cx, cy, cw, ch );
797 this.classname = "CtrlWin";
798 this.keys = new Object();
799 this.tellOk = function() {};
800 }
801 activate( tellOk ) {
802 this.tellOk = tellOk;
803 window.onkeydown = this.onkeydown.bind( this );
804 window.onkeyup = this.onkeyup.bind( this );
805 }
806 onkeydown( e ) {
807 this.keys[ e.which ] = true;
808 this.keytype( e.which );
809 }
810 onkeyup( e ) {
811 this.keys[ e.which ] = false;
812 }
813 keytype( key ) {
814 //プログラマーがインスタンスにて上書きすること
815 }
816 keysens( key ) {
817 //プログラマーがインスタンスにて上書きすること
818 }
819 hitanykey() {
820 return new Promise( function( tellOk ) {
821 let backup = window.onkeydown;
822 window.onkeydown = function( e ) {
823 window.onkeydown = backup;
824 tellOk();
825 }
826 } );
827 }
828 }
829
830 //---[C] MenuObject
831
832 class MenuObject extends CtrlWin {
833 constructor( id, menu, cx, cy, cw, ch ) {
834 super( id, cx, cy, cw, ch );
835 this.classname = "MenuObject";
836 this.title = menu.title;
837 this.items = menu.items;
838 this.onselect = menu.onselect;
839 this.oncursormove = menu.oncursormove;
840 this.cursorY = 0;
841
842 //check. ウィンドウのサイズを自動決定
843 if( this.cw == null ) {
844 //半角を1文字、全角を2文字で数えるlength関数
845 //https://javascript.programmer-reference.com/javascript-han1zen2/
846 let getLen = function( str ){
847 let result = 0;
848 for( let i = 0; i < str.length; i++ ) {
849 let chr = str.charCodeAt(i);
850 if( ( chr >= 0x00 && chr < 0x81 )
851 || ( chr === 0xf8f0 )
852 || ( chr >= 0xff61 && chr < 0xffa0 )
853 || ( chr >= 0xf8f1 && chr < 0xf8f4 ) ) {
854 //半角文字の場合は1を加算
855 result += 1;
856 } else {
857 //それ以外の文字の場合は2を加算
858 result += 2;
859 }
860 }
861 //結果を返す
862 return result;
863 };
864 this.cw = Math.max( ...this.items.map( item => Math.ceil( getLen( item.name ) / 2 ) ) ) + 2;
865 }
866 if( this.ch == null ) {
867 this.ch = this.items.length + 3;
868 }
869 }//constructor
870
871 //---[M] MenuObject.keytype
872
873 keytype( key ) {
874 let selectedItem;
875 switch( Number( key ) ) {
876 case 38:
877 if( this.cursorY > 0 ) this.cursorY --;
878 //カーソル移動時
879 if( this.oncursormove ) {
880 //---oncursormove実行場所
881 //この{..}ブロックをスコープにしてonselectの内容を実行する
882 //使用可能変数群
883 let local = {
884 selectedItem : this.items[ this.cursorY ],
885 parentMenu : this,
886 }
887 //関数をここで定義し直すと関数の中でこのブロックの変数が使える。
888 let exec_here;
889 eval( "exec_here = " + this.oncursormove.toString() );
890 exec_here();
891 }
892 break;
893 case 40:
894 if( this.cursorY < this.items.length - 1 ) this.cursorY ++;
895 //カーソル移動時
896 if( this.oncursormove ) {
897 //この{..}ブロックをスコープにしてonselectの内容を実行する
898 //使用可能変数群
899 let local = {
900 selectedItem : this.items[ this.cursorY ],
901 parentMenu : this,
902 }
903 //関数をここで定義し直すと関数の中でこのブロックの変数が使える。
904 let exec_here;
905 eval( "exec_here = " + this.oncursormove.toString() );
906 exec_here();
907 }
908 break;
909 case 32: //space
910 selectedItem = this.items[ this.cursorY ];
911 this.tellOk( selectedItem );
912 break;
913 case 88: //x
914 //メニューを閉じる
915 this.tellOk( null );
916 break;
917 }
918 }//keytype
919
920 //---[M] MenuObject.draw
921
922 draw( cc ) {
923 //枠
924 super.draw( cc );
925 let gx = this.cx * 16;
926 let gy = this.cy * 16;
927 let gw = this.cw * 16;
928 let gh = this.ch * 16;
929
930 //タイトル
931 cc.fillStyle = "black";
932 cc.fillText( this.title, gx, gy + 16 );
933
934 //項目
935 cc.save();
936 cc.translate( gx + 16, gy + 16 + 16 );
937 for( let i = 0; i < this.items.length; i++ ) {
938 let item = this.items[ i ];
939
940 gx = 0;
941 gy = i * 16;
942 gw = this.cw * 16 - 32;
943
944 if( i == this.cursorY ) {
945 cc.fillStyle = "black";
946 cc.fillRect( gx, gy, gw, 16 );
947 cc.fillStyle = "white";
948 } else {
949 cc.fillStyle = "black";
950 }
951 cc.fillText( item.name, gx, gy + 16 );
952 }
953 cc.restore();
954
955 }//draw
956 }
957
958 //---[C] Serif 画面にセリフを表示
959
960 class Serif extends CtrlWin {
961 constructor( id, cx, cy, cw, ch, cpadding, app ) {
962 super( id, cx, cy, cw, ch );
963 this.classname = "Serif";
964 this.matrixCx = Math.floor( this.cx + cpadding );
965 this.matrixCy = Math.floor( this.cy + cpadding );
966 this.matrixCw = this.cw - cpadding * 2;
967 this.matrixCh = this.ch - cpadding * 2;
968 this.app = app;
969
970 this.locateCx = 0;
971 this.locateCy = 0;
972
973 //文字描画はマス目状に行う
974 this.matrix = new Array();
975 for( let y = 0; y < this.matrixCh; y++ ) {
976 this.matrix[ y ] = new Array();
977 }
978
979 //文字列をbufferに置き、intervalで1文字ずつ順次送り出す
980 this.buffer = "";
981 this.bufferTime = 0;
982 this.bufferTimeMax = 100;
983
984 this.blinkerFlg = false;
985 }
986
987 //---[M] Serif.frame
988
989 frame() {
990 //check. 文字列がない場合は行わない
991 if( this.buffer == "" ) return;
992
993 //check. 時間に満たない場合は行わない
994 this.bufferTime += this.app.timerMs;
995 if( this.bufferTime < this.bufferTimeMax ) return;
996
997 this.bufferTime = 0;
998
999 //1文字送り出す
1000 let letter = this.buffer.substr( 0, 1 ); //取り出し
1001 this.buffer = this.buffer.substr( 1 ); //buffurを減らす
1002 this.printLetter( letter );
1003 //check. 文字列すべて送り出した?
1004 if( this.buffer == "" ) {
1005 this.tellOk();
1006 return;
1007 }
1008 }
1009 print( message ) {
1010 return new Promise(
1011 function( tellOk ) {
1012 this.buffer = message;
1013 this.tellOk = tellOk;
1014 }.bind( this )
1015 );
1016 }
1017 printLetter( letter ) {
1018 switch( letter ) {
1019
1020 case "\n": //改行文字
1021 this.locateCx = 0;
1022 this.locateCy ++;
1023 break;
1024
1025 default: //通常文字
1026 this.matrix[ this.locateCy ][ this.locateCx ] = letter;
1027 this.locateCx ++;
1028 //check. 右端に来た?
1029 if( this.locateCx == this.matrixCw ) {
1030 //折り返し
1031 this.locateCx = 0;
1032 this.locateCy ++;
1033 }
1034
1035 }//switch
1036
1037 //check. 下端に来た?
1038 if( this.locateCy == this.matrix.length ) {
1039 //スクロール
1040 this.matrix.shift();
1041 this.matrix.push( new Array() );
1042 this.locateCy --;
1043 }
1044 }
1045
1046 //Serif
1047
1048 draw( cc ) {
1049 super.draw( cc );
1050 cc.save();
1051 cc.translate( this.matrixCx * 16 + this.padding, this.matrixCy * 16 + this.padding );
1052 //マス目状に文字を出力する
1053 let gx, gy;
1054 for( let y = 0; y < this.matrixCh; y++ ) {
1055 for( let x = 0; x < this.matrixCw; x++ ) {
1056 let letter = this.matrix[ y ][ x ];
1057 //check. マスには文字がない?
1058 if( typeof letter === "undefined" ) continue;
1059 gx = x * 16;
1060 gy = y * 16;
1061 cc.fillStyle = "black";
1062 cc.fillText( letter, gx, gy + 16 );
1063 }
1064 }
1065 //入力を促す▼の点滅
1066 if( this.blinkerFlg ) {
1067 cc.save();
1068 cc.translate( gx + 16, gy + 3 );
1069 cc.beginPath();
1070 cc.moveTo( 0, 0 );
1071 cc.lineTo( 16,0 );
1072 cc.lineTo( 8,13 );
1073 cc.closePath();
1074 cc.fillStyle = "black";
1075 cc.fill();
1076 cc.restore();
1077 }
1078 cc.restore();
1079 }
1080
1081 //---[M] Serif.hitanykey
1082
1083 hitanykey() {
1084 this.blinkerFlg = true;
1085 let anm = new Anm();
1086 anm.frame = function() {
1087 this.blinkerFlg = ! this.blinkerFlg;
1088 return true;
1089 }.bind( this );
1090 anm.timer = 500;
1091 this.app.anms.push( anm );
1092
1093 return new Promise( function( tellOk ) {
1094 let backup = window.onkeydown;
1095 window.onkeydown = function( e ) {
1096 window.onkeydown = backup;
1097 tellOk();
1098 }
1099 }.bind( this ) ).then( function() {
1100 this.app.anms.splice( this.app.anms.indexOf( anm ), 1 );
1101 this.blinkerFlg = false;
1102 }.bind( this ) );
1103
1104 }
1105
1106 }//Serif
1107
1108
1109 </script>
1110 <style>
1111 </style>
1112 </head>
1113 <body onload="onloadx()" style="
1114 " onclick="location.reload()">
1115 <canvas id="test" width="512" height="448" style="
1116 border : solid 2px black;
1117 border-radius : 3px;
1118 box-shadow : 3px 3px 0px lightgray;
1119 float:left;
1120 ">
1121 </canvas>
1122 <div style="float:left; width:512px; padding-left:1em;">
1123 上下:↑↓キー、決定:スペースキー、キャンセル:xキー
1124 「どうぐ」、「C1」、「やくそう」でサブメニューの動きを確認できます。
1125 (xキーで1つ1つ戻ります)
1126
1127 しらべるを選ぶと、戦闘画面になります。
1128 戦闘画面でも、一人目の どうぐ→やくそう→C1 でサブメニューの動作を確認できます。
1129
1130 </div>
1131 <script>
1132 div = document.getElementsByTagName( "div" )[ 0 ];
1133 div.innerHTML = div.innerHTML.replace( /\n/g, "<BR>" );
1134 </script>
1135 </body>
1136 </html>