『こんにちはPython』のスカッシュゲーム(壁打ちテニス)を JavaScript に写経してみた!(前編、プログラミング初心者向け)
みなさま、こんにちは。ハーツテクノロジーの山崎です。(この記事は、業務とは直接無関係な記事ですが、業務で得られた知見が間接的に随所に織り込まれていると思われます。)
この記事のきっかけは「こんにちはPython」という本です。
プログラミング入門マンガ『こんにちはPython』の発売日まで、あと10日! 自分で作画した描き下ろしマンガは35年ぶりだからドキドキ。コマ割りまで担当した描き下ろしの『マンガでわかる小説入門』(作画・横山えいじ/ダイヤモンド社)からでも15年だ。https://t.co/tbOAnCQYZ3
— すがやみつる@『こんにちはPython』発売中&遠隔授業中! (@msugaya) April 19, 2020
『こんにちはPython』の見本が到着。こんな時なので、ひとり静かに打ち上げしています。 pic.twitter.com/jiyH1vGEIR
— すがやみつる@『こんにちはPython』発売中&遠隔授業中! (@msugaya) April 22, 2020
よい本です。なんと言っても、まんがであることが最高です。しかも、「ゲームセンターあらし」ですよ!(炎のコマは出ないけど)
あと、小学生にもわかるよう、丁寧に解説されている点もすばらしいです。めんどくさいから、「そういうもの」で片付けたいのがひとの心理だと思うのですが、そこはすがやみつる先生のやさしさなのだ、と思っています。
Python -> JavaScript に写経
で、この本、まんが版『こんにちはPython』では Python で説明されていますが、JavaScript で動かすこと(書き直してみること)にチャレンジしてみました。JavasScript で書くおすすめポイントはなんと言っても「ブラウザがあれば動いちゃうところ」です。
Python のコードはこちら↓にあります。
ゲームセンターあらしと学ぶプログラミング入門
まんが版『こんにちはPython』
https://www.m-sugaya.jp/manga_python/
(って、この↑ページからPythonのコードがダウンロードできるし、、、知らずに、マジメに本を見ながらコードを打ち込みました)
ということで、まんが版『こんにちはPython』のスカッシュゲームを JavaScript に写経(書き直し)してみました。その結果がこちら↓。
写経のよいところは、それぞれのプログラミング言語の違い(よいところとか)や実行環境、ライブラリの違いがわかって、いろいろと発見があることです。
せっかくですので、わたしも小学生向けに、JavaScript の解説をしてみようと思います。ただ、志半ばで挫折し、前編と後編に分けました。前編までは小学生でもなんとか理解できるように努力はしました。が、前編で力尽き、後編では、おとな向けの解説になっています。ごめんなさい。
JavaScript コードの解説
はじめに、JavaScript は、いろいろなブラウザで動かすことができるのですが、ここではパソコン版の Google Chrome で動作を確認したコードを解説します。他のブラウザでは、動かないかもしれません(動くかもしれません)。すみませんが、Google Chrome は Windows, Mac ともに無料でダウンロードできますので、Google Chrome で確認してください。
さてここからは、すがやみつる先生にならって、なるべく小学生でも理解できるようにコードの解説をしてみたいと思います。
9のステップにわけて解説していきます。それぞれのステップで、プログラムが動くかどうか?理解できたかどうか?確認しながらすすめられるように書きました。
- 1, 事前準備、HTML を少し書く
- 2, 表示エリア div, canvas を用意
- 3, はじめの一歩(表示してみる)
- 4, canvasにボールを描いてみる
- 5, アニメーションさせる!
- 6, 壁の登場!
- 7, 「ゲームオーバー」のコードを書く
- 8, ラケットを動かす
- 9, 最後に改造!
次のステップに行くは、前のステップにコードを追加する形で書いています。ちいさい単位で順番に理解していけば(おそらく)小学生でもわかるはず、、、です。
まんが版『こんにちはPython』が手元に無くても、わかるようには書いたつもりですが、やはり、本が手元にあったほうがわかりやすいと思います。
特にコードの解説に興味がない方は(おとなの方とかは)サクッと読み飛ばしてください。(おとな向けの記事は後編に書きます。)
それでは、はじめましょう。
1, 事前準備、HTML を少し書く
JavaScript でGUIを描くには、ブラウザの機能を使うのがラクチンです。ってか、もともとブラウザ内でプログラムを動かすためのスクリプトとして誕生したのが JavaScript なので、ブラウザ以外の選択肢を探すのは野暮ってことで👍。
もとの Python のコードでは、GUI の表示にライブラリ tkinter を使っていましたが、JavaScript はブラウザで動かすのでブラウザに表示させる HTML ファイルを作り、HTML ファイルの中に JavaScript のコードを書きます。HTML の書き方について語り始めるときりがないので、ここではプログラミングの話に集中する目的として最小限のタグにしぼり、body タグと script タグの2つのタグだけ使うことにしました。
HTML の全体は、こんな感じ。
<body></body>
<script>
// ここにJavaScript のコードが入ります。
</script>
HTML の部分はこれだけ😁
WebSquashGame.html とかファイルを作って、コードを書いて、ブラウザで開いてみると、真っ白な画面が表示されるはずです。
ちなみに、コードを書くツール(「エディタ」といいます)は、Visual Studio Code がおすすめです。無料だし、書いたコードに色がつくので、間違いに気が付きやすいです。あー、でも、デフォルト設定が英語ですね。日本語表示に変えるには、拡張パックが必要、、、うーん。小学生には難しいかも。おとなの人にたのんで入れてもらってください😅<日本語拡張パック
(あー、あとで気がついたのだけど、メールアドレスを持っているなら、CodePen にアカウントを作るのもありかもしれない、、、。おとななら問題なくできると思うけど、さすがに小学生には無理かな。)
2, 表示エリア div, canvas を用意
Python では、GUI の表示に外部のライブラリ tkinter を使っていましたが、JavaScript ではブラウザのAPI、document.createElement()
, document.body.appendChild()
を使って表示エリアを用意します。
このコードで用意しているタグ(表示用のエリア)は、 メッセージ表示用に div タグと、お絵かきするキャンバス canvas タグの2つです。
cv.setAttribute()
はキャンバスの属性を設定しています。ここではキャンバスの縦横の大きさを指定しています。
ということで、先程の HTMLファイルの <script> ... </script>
の ... の部分に、JavaScript のコードをがんばって書いていきます。ここに書いたコードが、ブラウザを開いたときに実行され、うまく動けば実行結果が表示される仕組みです。
(各APIのおとな向けの説明はこのあたり createElement、appendChild、setAttribute にあります。)
// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
// キャンバス表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
余談ですが、以下↓のように、HTML側にタグを書いて、document.querySelector()
で参照しても結果は↑のコードと同じになります。HTML の一般的な書き方では、コンテンツの構造を HTMLタグで書いて、イベントなどの動的なアクションを JavaScript で書くといった棲み分けをします。ただ、今回は HTML タグの記述は減らして、JavaScript で構造からすべて書いてしまう方針で行きます。
<body>
<div id="01">
<canvas id="02" width="640px" height="480px" >
</body>
<script>
const div = document.querySelector('div#01')
const cv = document.querySelector('canvas#02')
// (以下省略)
</script>
(おとな向けの説明はこのあたり querySelector、Canvas_API にあります。)
3, はじめの一歩(表示してみる)
では、ブラウザに表示するところまでコードを全部書いてみます。
メッセージは innerText に表示したい文字列を代入する形で('=' の右に)書くと表示されます。
div.innerText = "はじめの一歩"
キャンバスは、コンテキストを取得して、コンテキストのAPI(ctx.fillStyle = 'silver'
と ctx.fillRect()
)を呼ぶと描画されます。
const ctx = cv.getContext( '2d' ) // canvasからコンテキストを取得
// 画面クリア
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )
Python のコードでは四角を 'white' 白で塗っていましたが、ブラウザの背景もデフォルトで白いため、白い背景に白い四角を描いても見えないので、'silver' に変えました。
(おとな向けの説明はこのあたり innerText、fillStyle、fillRect にあります。)
さて、以下の 23 行のコードを HTML ファイル(WebSquashGame.html)に書いて、保存して、ブラウザで開いてみてください。プログラミングのはじまりです。
<body></body>
<script>
// はじめの一歩
// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "はじめの一歩"
// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )
// 画面クリア
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )
</script>
このような結果が表示されるはずです。(↓ CodePen での実行結果)
表示されましたか?
表示できた方、おめでとうございます🎉👍。無事にプログラミングの一歩を踏み出せました。
表示されなかった方😱は、おそらく、コードのどこかに間違いがあると思うので、「F12」キーを押して、Console にエラーメッセージが無いか確かめてください。エラーメッセージの示すところ(前後)をよーく見てみてください。なにかが違っているはず。がんばって見つけてね!
4, canvasにボールを描いてみる
プログラミングをうまくすすめるためには、なるべく小さい単位で動作を確認しながら、少しずつ進むことが得策です。
時間がもったいないと、山の頂上まで一気に駆け上がろうとして、足を滑らして転落したら、場所もさっぱりわからない状況、すなわち遭難してしまいます。(わたしは、そんな経験をもう何十回もしています😅。)
ところどころに休憩ポイントを設けて、どのあたりにいるのかを確認しながら進むほうが、手戻りの時間が節約できて、結果的に早く目的地に着けるということです。急がば回れ。ウサギとカメのお話ですね。
前のステップが確認できたら次のステップに進みます。
次のステップは、ボールを描きます。
まず、ボールの大きさや表示する位置は、あとから変えられるように、変数や定数として名前をつけて確保します。
あとから変えるのが、変数。変えない(名前だけ付ける)のが、定数。です。
JavaScript では、変数には let
、定数には const
を付けて書きます。少し前までは変数に var
を付けていましたが、var
だと、スコープ(見える範囲)が広すぎて、間違って書き換えちゃったりするから、なるべくスコープの狭い let
を使いましょう、、、ってことになっています。
// ボールを描く
let ball_ichi_x = 100
let ball_ichi_y = 100
const ball_size = 30
(おとな向けの説明はこのあたり let、const にあります。)
次に、ボールを描きます。ボールを描く位置は、用意した変数 ball_ichi_x, ball_ichi_y を使います。
具体的には、次のようなコードでボール(円)を描画します。ctx.arc()
はボールというより「円弧を描くAPI」のことなのですが、ぐるっと一周すれば「円」になります。その「ぐるっと一周」が 2 * Math.PI
というコードです。(このあたりも、小学生には難しすぎですね。残念ながらわかりやすく説明することができません。ごめんなさい。きっと、中学生か高校生にあたりで習うと思う。たぶん。)
ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()
(おとな向けの説明はこのあたり beginPath、arc、fill にあります。)
(おとな向けの追記があります。すがやみつる先生は小学生向けに、変数名にローマ字 ball_ichi_x, ball_ichi_y などを使って解説していますが、おとなのプログラマの方は英語で統一しましょう。ball_position_x とか、ball_pos_x とか。まぁ、x, y って書いたら、たいていは位置のことなので、 ball_x, ball_y が短いしベストかもしれません。)
まとめると次のようなコードになります。
<body></body>
<script>
// canvasにボールを描くテスト(その1)
// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "canvasにボールを描くテスト"
// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )
// 画面クリア
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )
// ボールを描く
let ball_ichi_x = 100
let ball_ichi_y = 100
const ball_size = 30
ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()
</script>
ここで、関数の考え方を導入します。
関数は、プログラミングの世界ではとてもよく使う定番な機能(プログラミング手法)で、長く煩雑になりがちなコードをまとめて名前をつけておくことで、後から名前だけで呼び出すすことができるようになる、という便利な機能です。
次の例では、画面クリアのコードにdraw_screen()
という名前を付けて、ボールを描くコードにdraw_ball()
という名前を付けて関数にしています。あとで、これらの関数を呼び出すように変更します。
まず、「画面クリア」の2行のコードを draw_screen()
という名前の関数に変更します。
// 画面クリア
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )
↓
// 画面クリア
function draw_screen() {
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )
}
そして、「ボールを描く」の4行のコードを draw_ball()
という名前の関数に変更します。
// ボールを描く
ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()
↓
// ボールを描く
function draw_ball() {
ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()
}
(おとな向けの説明はこのあたり Functions にあります。)
でもって、まとめて関数を呼び出します。呼び出すことで関数の中が実行されます。
// テストなので1回だけ実行
draw_screen() // 画面クリア
draw_ball() // ボールを描く
さて、たくさん書いちゃいましたが、すべてをまとめると次の 42 行になります。
関数にする前と、関数にしたあとは、コードは違うのですがブラウザで開くとどちらも同じ結果になるので、見た目はまったく同じで区別がつかないはずです。
<body></body>
<script>
// canvasにボールを描くテスト(関数版)
// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "canvasにボールを描くテスト"
// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )
// 画面クリア
function draw_screen() {
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )
}
// ボールを描く
let ball_ichi_x = 100
let ball_ichi_y = 100
const ball_size = 30
function draw_ball() {
ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()
}
// テストなので1回だけ実行
draw_screen() // 画面クリア
draw_ball() // ボールを描く
</script>
きちんと書けると、こうなるはず↓。確認重要。
赤いボールが表示されたかな?
長くなってきたので、ブラウザで開く前に、よーく見てね。
5, アニメーションさせる!
このあたりから難しくなるのけど、いちばん楽しいところなので、踏ん張りどころです!張り切って行きましょう!
まず、ボールを移動するための変数を確保します。
// ボールを移動
let ball_idou_x = 15
let ball_idou_y = -15
次に、ボールを移動するコードを書いて関数 move_ball()
にまとめます。
move_ball()
が呼ばれたらボールの位置をボールの移動量だけ足して動かしています。
function move_ball() {
// あとで、ここに「壁」と「ゲームオーバー」のコードが入ります
// ボールを移動
ball_ichi_x += ball_idou_x
ball_ichi_y += ball_idou_y
}
このmove_ball()
関数には、あとで「壁」のコードや「ゲームオーバー」のコードが追加されますので、覚えておいてください。
ちなみに、すがやみつる先生の Python のコードは、次のように canvas のエリア内のみ移動するように if 文で囲って書かれています。やさしいですね。しかし、わたしは、このあと「壁」のコードを書くので if 文は不要と勝手に判断し、シンプルに書いちゃいます😁。
function move_ball() {
// ボールを移動
if ( ball_ichi_x+ball_idou_x >= 0 ) {
if ( ball_ichi_x+ball_idou_x <= cv_w ) {
ball_ichi_x += ball_idou_x
}
}
if ( ball_ichi_y+ball_idou_y >= 0 ) {
if ( ball_ichi_y+ball_idou_y <= cv_h ) {
ball_ichi_y += ball_idou_y
}
}
}
そして、アニメーションの最大のキモとなるAPI setInterval()
の登場です。
こちらもブラウザに用意されている関数で、指定した時間ごとに関数を繰り返し呼び出してくれるという便利な機能です。数字 50 が呼ばれる時間間隔なので、ここでは 50ms ごとに game_loop() 関数が繰り返し呼ばれます。
繰り返し呼ばれることで、動いているように表示されます。すなわち「アニメーション」します。
// ゲームの繰り返し処理
function game_loop() {
draw_screen() // 画面クリア
draw_ball() // ボールを描く
move_ball() // ボールを移動
// あとで、ここに「ラケットを描く」関数呼び出しが入ります
}
// ゲームのメイン処理
setInterval( game_loop, 50 ) // 20fps
(おとな向けの説明はこのあたり [setInterval] にあります。)
[setInterval]:https://developer.mozilla.org/ja/docs/Web/API/Window/setInterval
まとめると、こんな感じになります。
<body></body>
<script>
// アニメーションさせるテスト(その1)
// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "アニメーションさせるテスト(その1)"
// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )
// 画面クリア
function draw_screen() {
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )
}
// ボールを描く
let ball_ichi_x = 100
let ball_ichi_y = 100
const ball_size = 30
function draw_ball() {
ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()
}
// ボールを移動
let ball_idou_x = 15
let ball_idou_y = -15
function move_ball() {
// あとで、ここに「壁」と「ゲームオーバー」のコードが入ります
// ボールを移動
ball_ichi_x += ball_idou_x
ball_ichi_y += ball_idou_y
}
// ゲームの繰り返し処理
function game_loop() {
draw_screen() // 画面クリア
draw_ball() // ボールを描く
move_ball() // ボールを移動
// あとで、ここに「ラケットを描く」関数呼び出しが入ります
}
// ゲームのメイン処理
setInterval( game_loop, 50 ) // 20fps
</script>
ここで、ほんとに動くかどうか、アニメーションするかどうか確認してみましょう。このように👇表示されるはずです。
見えましたか?赤いボールが、チラッと、右上の方向に動いていきます。そして、見えなくなります。ここでは「壁」がないので、それで正しい動きになります。
見逃した人は↑の右下のすみにある「Rerun」(再実行)のボタンを押してみましょう。最初から実行されるので見えると思います。
6, 壁の登場!
ここで「壁(中ボス)」の登場です。
ボールを動かすと、壁を通り抜けて、どこまでも行ってしまい帰ってきません。「壁」のコードを書いていないからです。
「壁」は上下左右に4箇所あります。それぞれの壁にぶつかった場合、ボールを反転させるコードを書きます。
反転させるには、移動量にマイナス1を掛けることで実現しています。
この「壁」のコードは、関数 move_ball() の中に追加します。(書く位置がわかりにくいかもしれないので、全コードを見て確認してください)
// 左の壁に当たったのかの判定
if ( ball_ichi_x+ball_idou_x < 0 ) {
ball_idou_x *= -1
}
// 左の壁に当たったのかの判定
if ( ball_ichi_x+ball_idou_x > cv_w ) {
ball_idou_x *= -1
}
// 天井に当たったのかの判定
if ( ball_ichi_y+ball_idou_y < 0 ) {
ball_idou_y *= -1
}
// 床に当たったのかの判定(あとで「ゲームオーバー」のコードに変わります)
if ( ball_ichi_y+ball_idou_y > cv_h ) {
ball_idou_y *= -1
}
あと、ボールのサイズも小さくconst ball_size = 10
にしています。
はたして、中ボス、クリアなるか!!!コードの全文はこちら!長い!!!!
とは言っても 80行。まだまだ少ない方です😁。
<body></body>
<script>
// アニメーションさせるテスト(その2)
// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "アニメーションさせるテスト(その2)"
// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )
// 画面クリア
function draw_screen() {
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )
}
// ボールを描く
let ball_ichi_x = 0
let ball_ichi_y = 250
const ball_size = 10
function draw_ball() {
ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()
}
// ボールの移動
let ball_idou_x = 15
let ball_idou_y = -15
function move_ball() {
// 左の壁に当たったのかの判定
if ( ball_ichi_x+ball_idou_x < 0 ) {
ball_idou_x *= -1
}
// 左の壁に当たったのかの判定
if ( ball_ichi_x+ball_idou_x > cv_w ) {
ball_idou_x *= -1
}
// 天井に当たったのかの判定
if ( ball_ichi_y+ball_idou_y < 0 ) {
ball_idou_y *= -1
}
// ここに(床に当たる前に)あとで「ラケットに当たったのかの判定」のコードを入れます
// 床に当たったのかの判定(あとで「ゲームオーバー」のコードに変わります)
if ( ball_ichi_y+ball_idou_y > cv_h ) {
ball_idou_y *= -1
}
// ボールを移動
ball_ichi_x += ball_idou_x
ball_ichi_y += ball_idou_y
}
// ゲームの繰り返し処理
function game_loop() {
draw_screen() // 画面クリア
draw_ball() // ボールを描く
move_ball() // ボールの移動
// あとで、ここに「ラケットを描く」関数呼び出しが入ります
}
// ゲームのメイン処理
setInterval( game_loop, 50 ) // 20fps
</script>
ボールがずっと壁に跳ね返ってるアニメーションになればプログラミング成功です!!🎉
できたかな? ここまでくれば中ボスクリア👍です。おめでとう!!
7, 「ゲームオーバー」のコードを書く
アニメーションの次はいよいよ「ゲーム」にします。
アニメーションは、ずーーと、同じことを繰り返し表示しています。スタートも無ければ、ゲームオーバーもありません。これでは「ゲーム」とは言えません。
「ゲーム」にするには、具体的には、is_gameover という変数を用意して、true のときは「ゲームオーバー」とし、逆に false のときは、ゲーム中(ボールを動かすアニメーションをする)とします。このis_gameover という変数に状態を入れて、ゲーム中なのか、ゲームオーバー(スタート待ち)なのかを切り替えるやりかたは、わりとよく使われるプログラミングのテクニックです。
また、ここで、点数を計算するための変数 point も用意しちゃいましょう(あとで使います)。
let is_gameover = true // true のときは「ゲームオーバー」、false のときは「ゲーム中」
let point = 0
そして、ボールの位置をゲームスタートの状態に戻す「初期化」の関数を書き、マウスをクリックしたらこの関数が呼ばれるように書きます。
// ゲームの初期化
let is_gameover = true // true のときは「ゲームオーバー」、false のときは「ゲーム中」
let point = 0
function init_game() {
is_gameover = false
point = 0
ball_ichi_x = 0
ball_ichi_y = 250
ball_idou_x = 15
ball_idou_y = -15
div.innerText = "ゲームスタート!"
}
// クリックで再スタート
cv.onclick = ev => {
if ( is_gameover )
init_game() // ゲームの初期化
}
次に、スカッシュゲームにするために、壁の一つ(床の部分)をゲームオーバーのコードに書き換えます。
// 床に当たったのかの判定(あとで「ゲームオーバー」のコードに変わります)
if ( ball_ichi_y+ball_idou_y > cv_h ) {
ball_idou_y *= -1
}
↓
// ゲームオーバーの判定
if ( ball_ichi_y+ball_idou_y > cv_h ) {
is_gameover = true
div.innerText = "ゲームオーバー!"
return // ゲームオーバーなのでボールの移動はせずに関数から抜けて帰る
}
そして、大切なのが「ゲームオーバーのときはボールの移動はしない」というコード↓
if ( is_gameover ) return // ゲームオーバーなので関数から抜けて帰る
この「ゲームオーバーのときはボールの移動はしない」コードをボールを移動する関数function move_ball() {
に入れます。
ここまでをまとめると以下のコードになります。ここまでくると、いよいよゲームの土台になってきた!!という感じが伝わってくると思います。
<body></body>
<script>
// ゲームオーバーのテスト
// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "ゲームオーバーのテスト:クリックでスタート"
// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )
// 画面クリア
function draw_screen() {
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )
}
// ボールを描く
let ball_ichi_x = 0
let ball_ichi_y = 250
const ball_size = 10
function draw_ball() {
ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()
}
// ボールの移動
let ball_idou_x = 15
let ball_idou_y = -15
function move_ball() {
if ( is_gameover ) return // ゲームオーバーなので関数から抜けて帰る
// 左右の壁に当たったのかの判定
if ( ball_ichi_x+ball_idou_x < 0 ) {
ball_idou_x *= -1
}
if ( ball_ichi_x+ball_idou_x > cv_w ) {
ball_idou_x *= -1
}
// 天井の壁に当たったのかの判定
if ( ball_ichi_y+ball_idou_y < 0 ) {
ball_idou_y *= -1
}
// ここに(床に当たる前に)あとで、「ラケットに当たったのかの判定」のコードを入れます
// 床にあたったのかの判定を、ゲームオーバーの判定に変えた
if ( ball_ichi_y+ball_idou_y > cv_h ) {
is_gameover = true
div.innerText = "ゲームオーバー!"
return // ゲームオーバーなのでボールの移動はせずに関数から抜けて帰る
}
// ボールを移動
ball_ichi_x += ball_idou_x
ball_ichi_y += ball_idou_y
}
// ゲームの初期化
let is_gameover = true
let point = 0
function init_game() {
is_gameover = false
point = 0
ball_ichi_x = 0
ball_ichi_y = 250
ball_idou_x = 15
ball_idou_y = -15
div.innerText = "ゲームスタート!"
}
// クリックで再スタート
cv.onclick = ev => {
if ( is_gameover )
init_game() // ゲームの初期化
}
// ゲームの繰り返し処理
function game_loop() {
draw_screen() // 画面クリア
draw_ball() // ボールを描く
move_ball() // ボールの移動
// あとで、ここに「ラケットを描く」関数呼び出しが入ります
}
// ゲームのメイン処理
setInterval( game_loop, 50 ) // 20fps
</script>
ちゃんと書けると、こうなるはず!↓
ゲームっぽくなってきました。
ここまでくれば、あと少しです!!がんばれー!!!
8, ラケットを動かす
さて、最後に「ラケット」(=ラスボス)の登場です。ラケットにあたると point が加算されます。この中ボスがクリアができると、待望のゲームになります。
まず、ラケットの描画に必要となる変数と定数(racket_ichi_x, racket_size)を追加します。
そして、draw_racket()
という関数と、マウスの位置とラケットの位置が同じになるコードracket_ichi_x = ev.offsetX
も追加します。
// ラケットを描く
let racket_ichi_x = 0
const racket_size = 100
function draw_racket() {
ctx.fillStyle = 'yellow'
ctx.fillRect( racket_ichi_x, cv_h-10, racket_size, 8 )
}
// マウスの動きの処理
cv.onmousemove = ev => {
racket_ichi_x = ev.offsetX
}
でもって、draw_racket()
を geme_loop() 関数の中に追加して、
// ゲームの繰り返し処理
function game_loop() {
draw_screen() // 画面クリア
draw_ball() // ボールを描く
move_ball() // ボールの移動
draw_racket() // ラケットを描く ←これ
}
そして、move_ball()
関数の中に「ラケット」あたった判定コードを書き加えます。なんと、if 文が三つも!!!!ひー!!さすがに難しいか!!よーく見て撃破してください。
ここ👇がラスボス!!!
// ラケットに当たったのかの判定
if ( ball_ichi_y+ball_idou_y > cv_h-10 ) {
if ( racket_ichi_x < ball_ichi_x+ball_idou_x ) {
if ( ball_ichi_x+ball_idou_x < racket_ichi_x+racket_size ) {
ball_idou_y *= -1
point += 10
div.innerText = '得点:' + point
}
}
}
このラスボスをクリアすると、ようやく「ゲーム」になります。ここまで長い道のりでした!!!
さて!全部まとめると!これ👇
コードをよーく見比べて、実行してください。
<body></body>
<script>
// 「こんにちはPython」のスカッシュゲーム(壁打ちテニス)をJavaScriptで写経(最小版)
// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "スカッシュゲーム:マウスクリックでスタート!"
// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )
// 画面クリア
function draw_screen() {
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )
}
// ボールを描く
let ball_ichi_x = 0
let ball_ichi_y = 250
const ball_size = 10
function draw_ball() {
ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()
}
// ボールの移動
let ball_idou_x = 15
let ball_idou_y = -15
function move_ball() {
if ( is_gameover ) return
// 左右の壁に当たったのかの判定
if ( ball_ichi_x+ball_idou_x < 0 ) {
ball_idou_x *= -1
}
if ( ball_ichi_x+ball_idou_x > cv_w ) {
ball_idou_x *= -1
}
// 天井の壁に当たったのかの判定
if ( ball_ichi_y+ball_idou_y < 0 ) {
ball_idou_y *= -1
}
// ラケットに当たったのかの判定
if ( ball_ichi_y+ball_idou_y > cv_h-10 ) {
if ( racket_ichi_x < ball_ichi_x+ball_idou_x ) {
if ( ball_ichi_x+ball_idou_x < racket_ichi_x+racket_size ) {
ball_idou_y *= -1
point += 10
div.innerText = '得点:' + point
}
}
}
// ミスした時の判定
if ( ball_ichi_y+ball_idou_y > cv_h ) {
div.innerText = 'ゲームオーバー!得点:' + point
is_gameover = true
return
}
// ボールを移動
ball_ichi_x += ball_idou_x
ball_ichi_y += ball_idou_y
}
// ラケットを描く
let racket_ichi_x = 0
const racket_size = 100
function draw_racket() {
ctx.fillStyle = 'yellow'
ctx.fillRect( racket_ichi_x, cv_h-10, racket_size, 8 )
}
// マウスの動きの処理
cv.onmousemove = ev => {
racket_ichi_x = ev.offsetX
}
// ゲームの初期化
let is_gameover = true
let point = 0
function init_game() {
is_gameover = false
point = 0
ball_ichi_x = 0
ball_ichi_y = 250
ball_idou_x = 15
ball_idou_y = -15
div.innerText = "スカッシュゲーム:スタート!"
}
// クリックで再スタート
cv.onclick = ev => {
if ( is_gameover )
init_game() // ゲームの初期化
}
// ゲームの繰り返し処理
function game_loop() {
draw_screen() // 画面クリア
draw_ball() // ボールを描く
move_ball() // ボールの移動
draw_racket() // ラケットを描く
}
// ゲームのメイン処理
setInterval( game_loop, 50 ) // 20fps
</script>
コードが入力できれば、こうなる↓はず!!!
ここまでで最小版の完成です。お疲れ様でした!!!!😎👍🎉
しばらく遊んでみて、問題が無いか確認してください。
9, 最後に改造!
ここまでで、JavaScript で「ゲーム」を作るのに必要な最小の要素は説明しました。
ここからは、みなさんの豊かな発想で、好きなように改造してください。
プログラミングとは、発想しだい、組み合わせしだいで、だれも見たこともないゲームになったり、多くの人にたのしさを与える、とてもワクワクすることなのです。
どんな発想をするか、どんな改造をするか、それともゼロから新しいモノを作るのか、みなさんのエネルギーに期待しています。
ちなみに、「こんにちは Python」では、こんな感じ↓に仕上げています。どこに、どんな改造をしているのかは、コードをよーく見比べてみてください。もしくは、「こんにちは Python」の本を買って読んでみるのもよいかもしれません。
<body></body>
<script>
// 「こんにちは Python」のスカッシュゲーム(壁打ちテニス)を JavaScript で写経(音無し版)
// メッセージ表示エリア div の確保
const div = document.createElement( 'div' )
document.body.appendChild( div )
div.innerText = "スカッシュゲーム(音無し版):マウスクリックでスタート!"
// canvas表示エリア cv の確保
const cv_w = 640
const cv_h = 480
const cv = document.createElement( 'canvas' )
cv.setAttribute( 'width', cv_w )
cv.setAttribute( 'height', cv_h )
document.body.appendChild( cv )
const ctx = cv.getContext( '2d' )
// 画面クリア
function draw_screen() {
ctx.fillStyle = 'silver' // 'white'
ctx.fillRect( 0, 0, cv_w, cv_h )
}
// ボールを描く
let ball_ichi_x = 0
let ball_ichi_y = 250
const ball_size = 10
function draw_ball() {
ctx.beginPath()
ctx.arc( ball_ichi_x, ball_ichi_y, ball_size, 0, 2 * Math.PI )
ctx.fillStyle = 'red'
ctx.fill()
}
// ボールの移動
let ball_idou_x = 15
let ball_idou_y = -15
function move_ball() {
if ( is_gameover ) return
// 左右の壁に当たったのかの判定
if ( ball_ichi_x+ball_idou_x < 0 ) {
ball_idou_x *= -1
}
if ( ball_ichi_x+ball_idou_x > cv_w ) {
ball_idou_x *= -1
}
// 天井の壁に当たったのかの判定
if ( ball_ichi_y+ball_idou_y < 0 ) {
ball_idou_y *= -1
}
// ラケットに当たったのかの判定
if ( ball_ichi_y+ball_idou_y > cv_h-10 ) {
if ( racket_ichi_x < ball_ichi_x+ball_idou_x ) {
if ( ball_ichi_x+ball_idou_x < racket_ichi_x+racket_size ) {
ball_idou_y *= -1
if ( Math.random() < 0.5 )
ball_idou_x *= -1
const mes = Math.floor( Math.random() * 5 )
let message = ''
if ( mes == 0 )
message = 'うまい!'
if ( mes == 1 )
message = 'グッド!'
if ( mes == 2 )
message = 'ナイス!'
if ( mes == 3 )
message = 'よしッ!'
if ( mes == 4 )
message = 'すてき!'
point += 10
div.innerText = message + ' 得点:' + point
}
}
}
// ミスした時の判定
if ( ball_ichi_y+ball_idou_y > cv_h ) {
const mes = Math.floor( Math.random() * 3 )
let message = ''
if ( mes == 0 )
message = 'へたくそ!'
if ( mes == 1 )
message = 'ミスしたね!'
if ( mes == 2 )
message = 'あーあ、見てられないね!'
div.innerText = message + ' 得点:' + point
is_gameover = true
return
}
// ボールを移動
ball_ichi_x += ball_idou_x
ball_ichi_y += ball_idou_y
}
// ラケットを描く
let racket_ichi_x = 0
const racket_size = 100
function draw_racket() {
ctx.fillStyle = 'yellow'
ctx.fillRect( racket_ichi_x, cv_h-10, racket_size, 8 )
}
// マウスの動きの処理
cv.onmousemove = ev => {
racket_ichi_x = ev.offsetX
}
// ゲームの初期化
let is_gameover = true
let point = 0
function init_game() {
is_gameover = false
ball_ichi_x = 0
ball_ichi_y = 250
ball_idou_x = 15
ball_idou_y = -15
point = 0
div.innerText = "スカッシュゲーム:スタート!"
}
// クリックで再スタート
cv.onclick = ev => {
if ( is_gameover )
init_game() // ゲームの初期化
}
// ゲームの繰り返し処理
function game_loop() {
draw_screen() // 画面クリア
draw_ball() // ボールを描く
draw_racket() // ラケットを描く
move_ball() // ボールの移動
}
// ゲームのメイン処理
setInterval( game_loop, 50 ) // 20fps
</script>
最初に載せたサンプルは音が出ますが、こちらは音無しです。音の有無以外は同じです。
ごめんなさい。音のプログラムの説明は後編で解説させてください。ここに載せるにはちょっと難しい内容なので。
それでは、以上で、JavaScript プログラミングの解説を終わります。
おそらく、わからないところや聞きたいところもたくさんあると思います。がんばって、自分のちからで突破してほしいと思います。ですが、どーしても解決できないときは、おとなのひとやここ(インターネット)に質問することもありです。
本 まんが版『こんにちはPython』を買うのもおすすめします。
「あきらめたらそこでゲームオーバー」なので、コツコツとゆっくりでもよいので、たのしんで続けてください。
みなさまの豊かなプログラミングライフを応援しつつ。
おまけ
ここからは、おとな向けの記事になります。
うーん。小学生にプログラミングを解説するのって、とても難しいです。不可能とも思えてきました。
まだまだ、書き残したことがあります。それは「音を出す」と「スマホ対応」です。さらには「リファクリング」というか、canvas より SVG に書き換えてみたいとか、描画速度を最適化してみたいとか、そんなところです。
これらの解説は、小学生向けに書き続けることに限界を感じたので、別の記事に分けたいと思います。
書き終わりましたら、こちらにリンクを貼りますので、もうしばらくお待ちください。
書き終わりました。おとなの方はこちらからどうぞ→後編。小学生の方は、読んでもいいけど、たぶん理解できないと思います。
どうぞよろしくお願いいたします😅。
追記(2021-02-16):相互リンク追加
同じような JavaScript プログラミングの記事なのに、View 数がめっちゃ偏っているので相互リンクしてみる。タイトルの問題なのかなぁ??🤔
-
『こんにちはPython』のスカッシュゲーム(壁打ちテニス)を JavaScript に写経してみた!(前編、プログラミング初心者向け) ←この記事
-
『こんにちはPython』のスカッシュゲーム(壁打ちテニス)を JavaScript で写経してみた!(後編、プログラミング玄人向け)
よろしくお願いします。🙇♂️