2-6 (2) ソースコード


解説ページに戻る

01<!DOCTYPE html>
02<html lang="ja">
03<head>
04<meta charset="utf-8">
05<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0 user-scalable=no">
06<title>JavaScriptのテストプログラム</title>
07</head>
08<body style="background-image:url('example261_2.png');">
09<canvas style="position:absolute; top:0; bottom:0; left:0; right:0; margin:auto;" id="bg"></canvas>
10<script>
11
12//キャンバスの設定
13var winW = window.innerWidth;
14var winH = window.innerHeight;
15var canvas = document.getElementById("bg");
16var cnt = canvas.getContext("2d");
17var SCALE;
18var CWIDTH  = 640;
19var CHEIGHT = 480;
20if( winH < winW*CHEIGHT/CWIDTH )
21 winW = toInt(winH*CWIDTH/CHEIGHT);
22else
23 winH = toInt(CHEIGHT*winW/CWIDTH);
24canvas.width = winW;
25canvas.height = winH;
26SCALE = winW / CWIDTH;
27cnt.scale( SCALE, SCALE );
28cnt.textAlign = "center";
29cnt.textBaseline = "middle";
30
31//マウスとタップの判定
32var tapX = 0, tapY = 0, tapC = 0;
33
34canvas.addEventListener( "touchstart", touchStart );
35canvas.addEventListener( "touchmove", touchMove );
36canvas.addEventListener( "touchend", touchEnd );
37canvas.addEventListener( "touchcancel", touchCancel );
38function touchStart(event) {
39 event.preventDefault();
40 var rect = event.target.getBoundingClientRect();
41 tapX = event.touches[0].clientX-rect.left;
42 tapY = event.touches[0].clientY-rect.top;
43 transformXY();
44 tapC = 1;
45 if( audio[0] == undefined ) loadSnd();//サウンドスマホ対応
46}
47function touchMove(event) {
48 event.preventDefault();
49 var rect = event.target.getBoundingClientRect();
50 tapX = event.touches[0].clientX-rect.left;
51 tapY = event.touches[0].clientY-rect.top;
52 transformXY();
53}
54function touchEnd(event) { tapC = 0; }
55function touchCancel(event) { tapC = 0; }
56
57canvas.addEventListener( "mousedown", mouseDown );
58canvas.addEventListener( "mousemove", mouseMove );
59canvas.addEventListener( "mouseup", mouseUp );
60function mouseDown(event) {
61 var rect = event.target.getBoundingClientRect();
62 tapX = event.clientX-rect.left;
63 tapY = event.clientY-rect.top;
64 transformXY();
65 tapC = 1;
66 if( audio[0] == undefined ) loadSnd();//サウンドスマホ対応
67}
68function mouseMove(event) {
69 var rect = event.target.getBoundingClientRect();
70 tapX = event.clientX-rect.left;
71 tapY = event.clientY-rect.top;
72 transformXY();
73}
74function mouseUp(event) { tapC = 0; }
75
76function transformXY() {//実座標→仮想座標への変換
77 tapX = toInt(tapX/SCALE);
78 tapY = toInt(tapY/SCALE);
79}
80
81function toInt( val ) {//整数を返す関数
82 return parseInt(val);
83}
84
85function rnd( max ) {//乱数を返す関数
86 return toInt( Math.random()*max );
87}
88
89//サウンドの処理
90var audio = [];
91
92function loadSnd() {//読み込み
93 for( var i = 0; i < 3; i ++ ) {
94  audio[i] = new Audio( "example261_" + i + ".m4a" );
95  audio[i].load();
96 }
97}
98
99function playSnd( n ) {//再生
100 audio[n].loop = false;
101 if( n == 0 ) audio[n].loop = true;//BGMはループ再生
102 audio[n].play();
103}
104
105function stopBGM() {//BGMはループ出力なので停止する関数を用意
106 audio[0].pause();
107 audio[0].currentTime = 0;
108}
109
110//ブラウザを隠した時にBGMを停止、再度広げた時にBGMを再出力
111document.addEventListener( "visibilitychange", vcProc );
112function vcProc() {
113 if( document.visibilityState == "hidden" ) stopBGM();
114 if( document.visibilityState == "visible" && idx == 3 ) playSnd(0);
115}
116
117//画像ファイルの処理
118var img = [];
119var imgPre = [];
120
121function loadImg( n ) {//画像を読み込む
122 imgPre[n] = false;
123 img[n] = new Image();
124 img[n].src = "example261_" + n + ".png";
125 img[n].onload = function() { imgPre[n] = true; }
126}
127
128function drawChip( chip, dx, dy ) {//マップチップを表示
129 if( imgPre[0] != true ) return;
130 var sx, sy;
131 if( chip < 10 ) {
132  sx = 72;
133  sy = 24*chip;
134 }
135 else {
136  chip -= 10;
137  sx = 24*(chip%3);
138  sy = 24*toInt(chip/3);
139 }
140 cnt.drawImage( img[0], sx, sy, 24, 24, dx, dy, 24, 24 );
141}
142
143function drawCharacter( cno, dx, dy, dir, ani ) {//キャラクターを表示
144 if( imgPre[1] != true ) return;
145 var cw = 32;
146 var ch = 54;
147 var cofs = 96*cno;
148 if( cno == 3 ) cw = 52;
149 var sx = cw*ani + cofs;
150 var sy = ch*dir;
151 if( ani == -1 ) {//倒れている絵
152   cw = 56; ch = 32; sx = cofs; sy = 216;
153 }
154 cnt.drawImage( img[1], sx, sy, cw, ch, dx-cw/2, dy-ch, cw, ch );
155}
156
157function drawIlst( cno, dx, dy, flg ) {//イラストを表示 flgがtrueの時は体力の枠も表示
158 if( imgPre[1] != true ) return;
159 cnt.drawImage( img[1], cno*120, 256, 120, 120, dx, dy, 120, 120 );
160 if( flg == true ) cnt.drawImage( img[1], 440, 0, 40, 120, dx+120, dy, 40, 120 );
161}
162
163var bgX = 0;
164function drawBG() {//背景(宇宙)を表示
165 if( imgPre[2] != true ) return;
166 bgX = (bgX+1)%160;//スクロール
167 for( var i = 0; i < 5; i ++ ) cnt.drawImage( img[2], 160*i-bgX, 0 );
168}
169
170function fText( str, x, y, siz, col ) {//文字表示
171 cnt.font = siz + "px monospace";
172 cnt.fillStyle = col;
173 cnt.fillText( str, x, y );
174}
175
176function fRect( x, y, w, h, col ) {//矩形(塗り潰し)
177 cnt.fillStyle = col;
178 cnt.fillRect( x, y, w, h );
179}
180
181function sRect( x, y, w, h, col ) {//矩形(線)
182 cnt.lineWidth = 2;//線の太さ
183 cnt.strokeStyle = col;
184 cnt.strokeRect( x, y, w, h );
185}
186
187function fArc( x, y, r, col ) {//円
188 cnt.fillStyle = col;
189 cnt.beginPath();
190 cnt.arc( x, y, r, 0, Math.PI*2, false );
191 cnt.fill();
192}
193
194function setAlp( per ) {//透明度
195 cnt.globalAlpha = per/100;
196}
197
198function drawBtn( str, x, y, w, h, sofs ) {//ボタン(中心座標、幅、高さ、文字のオフセット位置を指定)
199 var ret = 0;
200 setAlp(60);
201 fRect( x-w/2+1, y-h/2+1, w-2, h-2, "#000" );
202 if( x-w/2 < tapX && tapX < x+w/2 && y-h/2 < tapY && tapY < y+h/2 ) {
203  if( tapC > 0 ) ret = 1; else setAlp(30);//ボタン内が押されているか判定
204  fRect( x-w/2+1, y-h/2+1, w-2, h-2, "#0ff" );
205 }
206 setAlp(100);
207 sRect( x-w/2+1, y-h/2, w-2, h, "#fff" );
208 sRect( x-w/2, y-h/2+1, w, h-2, "#fff" );
209 fText( str, x, y+sofs, 24, "#fff" );
210 return ret;
211}
212
213//ローカルストレージ
214var LS_KEYNAME = "ShiningStar";
215function saveLS( kno, val ) {//保存する
216 try { localStorage.setItem( LS_KEYNAME+kno, val ); } catch( e ) {}
217}
218
219function loadLS( kno ) {//読み込む
220 var val;
221 try { val = localStorage.getItem( LS_KEYNAME+kno ); } catch( e ) {}
222 if( val == null ) val = 0;
223 return val;
224}
225
226//変数の宣言
227var idx = 0;
228var tmr = 0;
229var pl_chr = 0;
230var chrX = [], chrY = [], chrXP = [], chrYP = [], chrDI = [], chrLife = [], chrWins = [];
231var winner;
232var bombX, bombY, bombT;
233
234//マップデータ用の二次元配列を作る
235var map = new Array();
236for( var y = 0; y < 20; y ++ ) {
237 map[y] = new Array();
238 for( var x = 0; x < 20; x ++ ) map[y][x] = 0;
239}
240
241var TITLE = "Shining Stars";
242var WALK_PTN = [ 0, 1, 0, 2 ];
243var CNAME = [ "ランド", "フラワー", "シェリー", "ナイト" ];
244var COUNTD = [ "3", "2", "1", "GO!" ];
245
246function hitChkBG( x, y ) {//背景の判定
247 if( x < 0 || x >= 480 || y < 0 || y >= 480 ) return 99;//画面外の場合
248 return map[ toInt(y/24) ][ toInt(x/24) ];
249}
250
251function hitChkCH( c1, c2 ) {//キャラ同士のヒットチェック
252 var dis = Math.sqrt( (chrX[c1]-chrX[c2])*(chrX[c1]-chrX[c2]) + (chrY[c1]-chrY[c2])*(chrY[c1]-chrY[c2]) );
253 return toInt( dis );
254}
255
256var sortN = [];
257function sortChr() {//ソート処理
258 var i, j, t;
259 for( i = 0; i < 4; i ++ ) sortN[i] = toInt(chrY[i])*10+i;
260 for( i = 0; i < 3; i ++ ) {
261  for( j = 0; j < 3-i; j ++ ) {
262   if( sortN[j] > sortN[j+1] ) { t = sortN[j]; sortN[j] = sortN[j+1]; sortN[j+1] = t; }
263  }
264 }
265}
266
267function searchChr( p ) {//サーチ処理
268 var i;
269 var ch = (p+1)%4;//ターゲットとするキャラ
270 if( chrLife[ch] == 0 ) {
271  for( i = 0; i < 2; i ++ ) {
272   ch = (ch+1)%4; if( chrLife[ch] > 0 ) break;
273  }
274 }
275 return ch;
276}
277
278//エフェクト
279var EF_MAX = 10;//同時に表示できる最大数
280var effectX = [], effectY = [], effectT = [];
281var ef_no = 0;
282
283function clrEffect() {
284 for( var i = 0; i < EF_MAX; i ++ ) {
285  effectX[i] = 0;
286  effectY[i] = 0;
287  effectT[i] = 0;
288 }
289 ef_no = 0;
290}
291
292function setEffect( x, y ) {
293 effectX[ef_no] = x;
294 effectY[ef_no] = y;
295 effectT[ef_no] = 10;
296 ef_no = (ef_no+1)%EF_MAX;
297}
298
299function drawEffect() {
300 var i;
301 for( i = 0; i < EF_MAX; i ++ ) {
302  if( effectT[i] == 0 ) continue;
303  setAlp(10*effectT[i]);
304  fArc( effectX[i], effectY[i], 6*(12-effectT[i]), "#f00" );
305  effectT[i] --;
306 }
307 setAlp(100);
308}
309
310function gameStart() {//ゲーム開始時に初期化する変数
311 var x, y;
312 chrX[0] =  5*24; chrY[0] = 5*24; chrXP[0] = 0; chrYP[0] = 0; chrDI[0] = 3; chrLife[0] = 100;
313 chrX[1] = 15*24; chrY[1] =  5*24; chrXP[1] = 0; chrYP[1] = 0; chrDI[1] = 2; chrLife[1] = 100;
314 chrX[2] = 15*24; chrY[2] = 15*24; chrXP[2] = 0; chrYP[2] = 0; chrDI[2] = 2; chrLife[2] = 100;
315 chrX[3] =  5*24; chrY[3] = 15*24; chrXP[3] = 0; chrYP[3] = 0; chrDI[3] = 3; chrLife[3] = 100;
316 bombX = 7+rnd(7);
317 bombY = 7+rnd(7);
318 bombT = 10;
319 for( y = 2; y < 19; y ++ ) for( x = 1; x < 19; x ++ ) map[y][x] = 0;
320 idx = 2;
321 tmr = 0;
322}
323
324window.onload = indexProc();
325function indexProc() {
326 var i, p, s, x, y;
327 tmr ++;
328 drawBG();
329 switch( idx ) {
330
331  case 0://初期化処理
332  loadImg(0);//背景画像の読み込み
333  loadImg(1);//キャラクター画像の読み込み
334  loadImg(2);//イラスト画像の読み込み
335  for( p = 0; p < 4; p ++ ) chrWins[p] = loadLS(p);
336  for( x = 1; x < 19; x ++ ) {
337   map[0][x] = 11; map[1][x] = 14;//上のフェンス
338   map[19][x] = 23;//下のフェンス
339  }
340  for( y = 2; y < 19; y ++ ) {
341   map[y][ 0] = 16;//左のフェンス
342   map[y][19] = 18;//右のフェンス
343  }
344  map[ 0][ 0] = 10; map[ 1][ 0] = 13;//左上角
345  map[ 0][19] = 12; map[ 1][19] = 15;//右上角
346  map[18][ 0] = 19; map[19][ 0] = 22;//左下角
347  map[18][19] = 21; map[19][19] = 24;//右下角
348  idx = 1;
349  break;
350
351  case 1://タイトル画面&キャラ選択
352  x = 110;
353  y = 80;
354  for( i = 0; i < 13; i ++ ) {
355   var col = "#bef"; if( tmr%16 == i ) col = "#fff";
356   fText( TITLE.charAt(i), x, y, 80, col );
357   if( TITLE.charAt(i) == "i" || (TITLE+" ").charAt(i+1) == "i" ) x += 30; else x += 40;
358  }
359  for( p = 0; p < 4; p ++ ) {
360   x = 80+160*p;
361   y = 240;
362   if( drawBtn( CNAME[p], x, y, 128, 160, 60 ) > 0 ) {
363    pl_chr = p;
364    clrEffect();
365    gameStart();
366    break;
367   }
368   drawIlst( p, x-60, y-80, false );
369   if( chrWins[p] > 0 ) fText( chrWins[p] + "wins", x, y+100, 24, "#ff8" );
370  }
371  if( drawBtn( "How to play", 320, 400, 180, 60, 0, 0 ) > 0 ) idx = 5;
372  break;
373
374  case 2://ゲーム開始演出
375  if( tmr == 40 ) {
376   playSnd(0);
377   idx = 3;
378   tmr = 0;
379  }
380  break;
381
382  case 3://ゲームメイン処理
383  for( p = 0; p < 4; p ++ ) {
384   if( chrLife[p] > 0 ) {
385    if( p == pl_chr ) {//ユーザーキャラの移動量(速度)
386     if( tapC > 0 ) {
387      if( tapY < chrY[p] ) { chrDI[p] = 0; chrYP[p] -= 4; }
388      if( tapY > chrY[p] ) { chrDI[p] = 1; chrYP[p] += 4; }
389      if( tapX < chrX[p] ) { chrDI[p] = 2; chrXP[p] -= 4; }
390      if( tapX > chrX[p] ) { chrDI[p] = 3; chrXP[p] += 4; }
391     }
392    }
393    else {//コンピューターキャラの移動量(速度)
394     var target = searchChr( p );//どの相手に向かっていくか
395     if( rnd(100) < 8 ) target = rnd(4);//ランダム性を入れる→膠着状態を防ぐことができる
396     if( chrY[target] < chrY[p] ) { chrDI[p] = 0; chrYP[p] -= 3; }
397     if( chrY[target] > chrY[p] ) { chrDI[p] = 1; chrYP[p] += 3; }
398     if( chrX[target] < chrX[p] ) { chrDI[p] = 2; chrXP[p] -= 3; }
399     if( chrX[target] > chrX[p] ) { chrDI[p] = 3; chrXP[p] += 3; }
400    }
401   }
402
403   for( i = 0; i < 4; i ++ ) {//他のキャラとぶつかったか?
404    if( i == p ) continue;
405    if( hitChkCH( p, i ) <= 24 ) {//ぶつかると互いに弾き飛ばされる
406     var foruser = ( pl_chr == p ) ? 1.2 : 1.0;
407     var xp = chrXP[p]; chrXP[p] = chrXP[i]; chrXP[i] = toInt( xp*foruser );
408     var yp = chrYP[p]; chrYP[p] = chrYP[i]; chrYP[i] = toInt( yp*foruser );
409     if( hitChkBG( chrX[i]+chrXP[i], chrY[i] ) < 10 ) chrX[i] += chrXP[i];
410     if( hitChkBG( chrX[i], chrY[i]+chrYP[i] ) < 10 ) chrY[i] += chrYP[i];
411    }
412   }
413
414   //上下左右の壁に向かってぶつかると跳ね返る
415   if( chrX[p] <  36 && chrXP[p] < 0 ) chrXP[p] *= -2;
416   if( chrX[p] > 444 && chrXP[p] > 0 ) chrXP[p] *= -2;
417   if( chrY[p] <  60 && chrYP[p] < 0 ) chrYP[p] *= -2;
418   if( chrY[p] > 444 && chrYP[p] > 0 ) chrYP[p] *= -2;
419   //移動量の最大値
420   if( chrYP[p] < -12 ) chrYP[p] = -12;
421   if( chrYP[p] >  12 ) chrYP[p] = 12;
422   if( chrXP[p] < -12 ) chrXP[p] = -12;
423   if( chrXP[p] >  12 ) chrXP[p] = 12;
424   //移動先が壁でなければ座標を変化させる
425   if( hitChkBG( chrX[p]+chrXP[p], chrY[p] ) < 10 ) chrX[p] += chrXP[p];
426   if( hitChkBG( chrX[p], chrY[p]+chrYP[p] ) < 10 ) chrY[p] += chrYP[p];
427   //移動量の減衰
428   chrXP[p] = toInt(chrXP[p]*0.8);
429   chrYP[p] = toInt(chrYP[p]*0.8);
430
431   //爆弾に触れたか?
432   if( hitChkBG( chrX[p], chrY[p] ) == 1 ) {
433    x = toInt(chrX[p]/24);
434    y = toInt(chrY[p]/24);
435    setEffect( 12+24*x, 12+24*y );
436    map[y][x] = 0;
437    chrLife[p] -= 25;
438    if( chrLife[p] < 0 ) chrLife[p] = 0;
439   }
440  }
441
442  s = 0; for( p = 0; p < 4; p ++ ) if( chrLife[p] > 0 ) s ++;//倒れていない人数を数える
443  if( s <= 1 ) {
444   stopBGM();
445   winner = -1; for( p = 0; p < 4; p ++ ) if( chrLife[p] > 0 ) winner = p;//勝者の番号
446   if( winner == pl_chr ) {//ユーザーが勝った場合は勝利回数を保存する
447    playSnd(1);//勝利ジングル
448    chrWins[winner] ++;
449    saveLS( winner, chrWins[winner] );
450   }
451   else {
452    playSnd(2);//敗北ジングル
453   }
454   idx = 4;
455   tmr = 0;
456  }
457
458  //爆弾の処理
459  bombT --;
460  if( bombT == 0 ) {
461   map[bombY][bombX] = 1;
462   bombX = 1+rnd(18);
463   bombY = 2+rnd(17);
464   bombT = 10;
465  }
466  break;
467
468  case 4://結果
469  if( tmr == 50 ) idx = 1;
470  break;
471
472  case 5://ゲーム説明
473  x = 320;
474  y = 80;
475  fText( "自分の操作するキャラを選ぶとスタート。", x, y, 28, "#fff" ); y += 60;
476  fText( "自キャラは画面の触れた位置に進みます。", x, y, 28, "#fff" ); y += 60;
477  fText( "体当たりすると互いに弾き飛ばされます。", x, y, 28, "#fd0" ); y += 60;
478  fText( "相手を爆弾に触れさせて体力を減らし、 ", x, y, 28, "#8ef" ); y += 60;
479  fText( "相手全員の体力をゼロにすると勝利です。", x, y, 28, "#8ef" ); y += 80;
480  if( drawBtn( "戻る", 540, y, 120, 60, 0 ) > 0 ) idx = 1;
481  break;
482
483 }
484
485 //画面の描画
486 if( idx == 2 || idx == 3 || idx == 4 ) {
487  //フェンス、床、爆弾
488  for( y = 0; y < 20; y ++ ) {
489   for( x = 0; x < 20; x ++ ) drawChip( map[y][x], x*24, y*24 );
490  }
491  //爆弾の表示
492  if( idx == 3 && bombT%2 == 0 ) fRect( bombX*24, bombY*24, 24, 24, "#f00" );
493  //キャラクターの表示
494  sortChr();
495  for( p = 0; p < 4; p ++ ) {
496   var n = sortN[p]%10;
497   var a = WALK_PTN[tmr%4];//キャラの動き
498   if( chrLife[n] == 0 ) a = -1;//ライフ0の時はやられている絵を表示
499   drawCharacter( n, chrX[n], chrY[n]+2, chrDI[n], a );
500  }
501  //前面のフェンスの表示
502  y = 18; for( x = 1; x < 19; x ++ ) drawChip( 20, x*24, y*24 );
503  //イラストと体力の表示
504  for( p = 0; p < 4; p ++ ) {
505   y = 120*p;
506   if( pl_chr == p ) { setAlp(50); fRect( 480, y, 160, 120, "#fff" ); setAlp(100); }
507   fRect( 610, y+110-chrLife[p], 20, chrLife[p], "#06f" );
508   fRect( 612, y+110-chrLife[p], 16, chrLife[p], "#28f" );
509   fRect( 614, y+110-chrLife[p], 12, chrLife[p], "#4af" );
510   drawIlst( p, 480, y, true );
511  }
512  if( tapC > 0 ) {//タップしている位置を表示
513   sRect( tapX-10, tapY-10, 20, 20, "#fff" );
514   fRect( tapX-20, tapY-1, 40, 2, "#ddd" );
515   fRect( tapX-1, tapY-20, 2, 40, "#ddd" );
516  }
517  drawEffect();//エフェクト
518 }
519
520 if( idx == 2 ) {
521  s = tmr%10;//文字の大きさと透明度
522  c = toInt(tmr/10);
523  setAlp((10-s)*10);
524  fText( COUNTD[c], 240, 240, (s+8)*12, "#fff" );
525  setAlp(100);
526 }
527
528 if( idx == 4 ) {
529  if( winner == -1 ) {
530   fText( "全員倒れました...", 240, 240, 36, "#fc0" );
531  }
532  else {
533   var NAM = CNAME[winner];
534   if( winner == pl_chr ) NAM = "あなた";
535   fText( "勝者は " + NAM + " です!", 240, 240, 36, "#8fc" );
536  }
537 }
538
539 setTimeout( indexProc, 100 );
540}
541
542</script>
543</body>
544</html>

キャラクターを動かす処理(383~429行)について
383forループで4体処理する。
384体力があるキャラだけ移動量の計算を行う。
385~391タップ入力がある場合、タップされている座標とユーザーキャラの座標を比べ、移動量(速度)を変化させる。
393~400コンピューターキャラは関数searchChr()で狙う相手を決め、その相手と自分の座標を比べ、移動量(速度)を変化させる。
403~412 他のキャラと衝突したかを調べる。2体の距離が24ドット以下なら衝突とし、互いの移動量を入れ替えることで弾き飛ばされる処理を実現している。
414~429ソースコードのコメントの通り次の処理を行っている
・上下左右の壁に向かってぶつかると跳ね返る
・移動量が一定値以上に大きくならないようにする
・425、426行で、まずX方向の先に壁がないならX座標の値を変化させる。次いでY方向の壁を調べY座標を変化させる
・移動量の減衰で、走っているユーザーキャラがタップを放すと少し滑って止まる動作を実現している

コンピューターが狙う相手を決める関数について

function searchChr( p ) {//サーチ処理
 var i;
 var ch = (p+1)%4;//ターゲットとするキャラ
 if( chrLife[ch] == 0 ) {
  for( i = 0; i < 2; i ++ ) {
   ch = (ch+1)%4; if( chrLife[ch] > 0 ) break;
  }
 }
 return ch;
}

まず ch = (p+1)%4 という式で、ランドが狙う相手はフラワー、フラワーの相手はシェリー、シェリーの相手はナイト、ナイトの相手はランドとします。 その相手の体力がゼロの場合は、その次の相手から検索し体力があるキャラを狙う相手をしています。


解説ページに戻る
お気軽にお問い合わせ下さい →