前回、C#で作成したクラッシュローラーをJavaScriptに書き直します。今回は通路の作成とプレイヤーの移動の処理をおこないます。
Contents
素材
画像として以下を使います。
入手元
また音源として以下を使います。
BGM ステージクリア時の効果音 ミス時の効果音 ゲームオーバー時の効果音 敵を倒したときの効果音 ローラー使用時の効果音
入手元
HTML部分
まずHTML部分を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>鳩でもわかるクラッシュローラー</title> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { background-color: #000; color: #fff; } #container { width: 100%; max-width: 960px; margin-right: auto; margin-left: auto; } .display-none { display:none; } </style> </head> <body> <div id = "container"> <canvas id = "can"></canvas> <div class = "display-none"> <img id = "player" src="./player.png" /> <img id = "enemy1" src="./enemy1.png" /> <img id = "enemy2" src="./enemy2.png" /> </div> </div> <script type='text/javascript' src='./map.js'></script> <script type='text/javascript' src='./main.js'></script> </body> </html> |
map.jsは以下の画像をもとに各ピクセルを数字に置き換えたものです。これが二次元配列mapに格納されます。
map.js
ここからダウンロード可能です。
定数
JavaScriptの定数部分を示します。その意味するところはコメントのとおりです。
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
const Direct = { // 各キャラクターの移動方向 Stop : 0, Up : 1, Down : 2, Left : 3, Right : 4, }; const POSITION = { POSITION_NONE : -1, POSITION_ROAD : 1, // 普通の通路 POSITION_BRIDGE_NS : 2, // 南北の橋 POSITION_BRIDGE_WE : 3, // 東西の橋 POSITION_BRIDGE_NS_CROSS : 4, // 東西の橋と通路が交差している点 POSITION_BRIDGE_WE_CROSS : 5, // 東西の橋と通路が交差している点 POSITION_BRIDGE_EDGE : 6, // 橋の開始地点 POSITION_BRIDGE_NEAR_EDGE : 7, // 橋の開始地点の周辺 }; const LEFT_MARGIN = 0; // Canvasの左側の余白 const TOP_MARGIN = 10; // Canvasの上側の余白 const EXPANSION_RATE = 1.6; // 拡大率(スマホでも表示できるギリギリのサイズ) const CHARACTOR_SIZE = 20; // キャラクタの大きさ const BORDER_WIDTH = 2; // 通路とそうでない部分の境界線の幅 const CANVAS_WIDTH = 310; // Canvasのサイズ const CANVAS_HEIGHT = 470; const INIT_PLAYER_X = 90; // プレイヤーの初期座標。これにEXPANSION_RATEを乗じたCanvas上の座標に描画される const INIT_PLAYER_Y = 198; const INIT_ENEMY1_X = 71; // 敵の初期座標 const INIT_ENEMY1_Y = 52; const INIT_ENEMY2_X = 109; const INIT_ENEMY2_Y = 52; const INIT_INTERVAL = 30; // 最初のタイマーのインターバル。ステージが進行するたびに短くする const ROLLER_THICKNESS = 10; // ローラーの厚さ const INIT_ADD_POINT_CRASH_ENEMY = 100; // 敵を倒したときに加算される点数の初期値 const MAX_REST = 6; // 残機の最大値 |
1 2 3 4 5 6 7 8 |
class Position { constructor(x, y) { this.X = x; this.Y = y; } } |
初期化の処理
Init関数で初期化の処理をおこないます。Canvasのサイズの設定をおこない描画ができるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
let canvas; let ctx; let mapSourceHeight; let mapSourceWidth; let interval = INIT_INTERVAL; let interval2 = INIT_INTERVAL * 1.5; let visitedColor = "rgb(0, 255, 0)"; // プレイヤーと敵のイメージ let playerImg; let enemy1Img; let enemy2Img; function Init() { canvas = document.getElementById('can'); ctx = canvas.getContext("2d"); canvas.width = CANVAS_WIDTH; canvas.height = CANVAS_HEIGHT; mapSourceHeight = map.length; mapSourceWidth = map[0].length; // プレイヤーが通路上のすべての点(正確には違う)を通過したらステージクリア // 通過したかどうかを管理する二次元配列を初期化する ClearVisits(); // プレイヤーを初期座標にセット InitPlayerPosition(); // プレイヤーと敵のイメージを変数に格納する playerImg = document.getElementById('player'); enemy1Img = document.getElementById('enemy1'); enemy2Img = document.getElementById('enemy2'); // タイマーのインターバルを初期値に設定する interval = INIT_INTERVAL; } |
ClearVisits関数はプレイヤーがその座標を通過したかどうかを管理する二次元配列を初期化する処理をおこなうためのものです。
1 2 3 4 5 6 7 8 9 10 11 |
let isVisits = []; function ClearVisits(){ isVisits = []; for (let y = 0; y < mapSourceHeight; y++) { let arr = []; for (let x = 0; x < mapSourceWidth; x++) { arr.push(false); } isVisits.push(arr); } } |
InitPlayerPosition関数はプレイヤーの座標を初期化する処理をおこなうためのものです。
1 2 3 4 5 6 7 |
let playerX = INIT_PLAYER_X; let playerY = INIT_PLAYER_Y; function InitPlayerPosition(){ playerX = INIT_PLAYER_X; playerY = INIT_PLAYER_Y; } |
描画処理
DrawClear関数はCanvas全体を黒で塗りつぶす処理をおこなうためのものです。
1 2 3 4 5 |
function DrawClear() { ctx.fillStyle = "rgb(0, 0, 0)"; ctx.fillRect(0, 0, canvas.width, canvas.height); } |
通路の描画
DrawRoad関数は通路を描画するためのものです。青でやや大きめの矩形を描画して、これを通路とそうでない部分の境界線とします。そのあとそのなかを白で塗りつぶします。またプレイヤーが通過した座標に相当する部分は緑色で塗りつぶします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
function DrawRoad(){ ctx.fillStyle = "rgb(0, 0, 255)"; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] > 0) ctx.fillRect( x * EXPANSION_RATE - BORDER_WIDTH + LEFT_MARGIN, y * EXPANSION_RATE - BORDER_WIDTH + TOP_MARGIN, CHARACTOR_SIZE + BORDER_WIDTH * 2, CHARACTOR_SIZE + BORDER_WIDTH * 2); } } ctx.fillStyle = "rgb(255, 255, 255)"; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] > 0) ctx.fillRect( x * EXPANSION_RATE + LEFT_MARGIN, y * EXPANSION_RATE + TOP_MARGIN, CHARACTOR_SIZE, CHARACTOR_SIZE); } } ctx.fillStyle = visitedColor; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] > 0 && isVisits[y][x]) ctx.fillRect( x * EXPANSION_RATE + LEFT_MARGIN, y * EXPANSION_RATE + TOP_MARGIN, CHARACTOR_SIZE, CHARACTOR_SIZE); } } } |
通路と通路が交わっている部分の上側(「橋」)を描画する処理をおこないます。DrawBridgeNS関数は南北にかかる橋、DrawBridgeWE関数は東西にかかる橋の描画をおこないます。あとは普通の通路を描画するときと同じ処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
function DrawBridgeNS(){ // 橋の輪郭を描画 ctx.fillStyle = "rgb(0, 0, 255)"; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] == POSITION.POSITION_BRIDGE_NS_CROSS) { ctx.fillRect( x * EXPANSION_RATE - BORDER_WIDTH + LEFT_MARGIN, y * EXPANSION_RATE + TOP_MARGIN, BORDER_WIDTH, CHARACTOR_SIZE); ctx.fillRect( x * EXPANSION_RATE + CHARACTOR_SIZE + LEFT_MARGIN, y * EXPANSION_RATE + TOP_MARGIN, BORDER_WIDTH, CHARACTOR_SIZE); } } } // 橋の内部を描画 ctx.fillStyle = "rgb(255, 255, 255)"; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] == POSITION.POSITION_BRIDGE_NS) ctx.fillRect( x * EXPANSION_RATE + LEFT_MARGIN, y * EXPANSION_RATE + TOP_MARGIN, CHARACTOR_SIZE, CHARACTOR_SIZE); } } // 橋の内部ですでにプレイヤーが通過している部分を描画 ctx.fillStyle = visitedColor; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if ((map[y][x] == POSITION.POSITION_BRIDGE_NS) && isVisits[y][x]) { ctx.fillRect( x * EXPANSION_RATE + LEFT_MARGIN, y * EXPANSION_RATE + TOP_MARGIN, CHARACTOR_SIZE, CHARACTOR_SIZE); } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
function DrawBridgeWE(){ // 橋の輪郭を描画 ctx.fillStyle = "rgb(0, 0, 255)"; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] == POSITION.POSITION_BRIDGE_WE_CROSS) { ctx.fillRect( x * EXPANSION_RATE + LEFT_MARGIN, y * EXPANSION_RATE - BORDER_WIDTH + TOP_MARGIN, CHARACTOR_SIZE, BORDER_WIDTH); ctx.fillRect( x * EXPANSION_RATE + LEFT_MARGIN, y * EXPANSION_RATE + CHARACTOR_SIZE + TOP_MARGIN, CHARACTOR_SIZE, BORDER_WIDTH); } } } // 橋の内部を描画 ctx.fillStyle = "rgb(255, 255, 255)"; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] == POSITION.POSITION_BRIDGE_WE) ctx.fillRect( x * EXPANSION_RATE + LEFT_MARGIN, y * EXPANSION_RATE + TOP_MARGIN, CHARACTOR_SIZE, CHARACTOR_SIZE); } } // 橋の内部ですでにプレイヤーが通過している部分を描画 ctx.fillStyle = visitedColor; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if ((map[y][x] == POSITION.POSITION_BRIDGE_WE) && isVisits[y][x]) { ctx.fillRect( x * EXPANSION_RATE + LEFT_MARGIN, y * EXPANSION_RATE + TOP_MARGIN, CHARACTOR_SIZE, CHARACTOR_SIZE); } } } } |
プレイヤーの描画
DrawPlayer関数が呼び出されるとプレイヤーの描画がおこなわれます。
1 2 3 4 5 6 7 8 9 |
function DrawPlayer() { ctx.drawImage( playerImg, playerX * EXPANSION_RATE + LEFT_MARGIN - 5, playerY * EXPANSION_RATE + TOP_MARGIN - 5, CHARACTOR_SIZE + 10, CHARACTOR_SIZE + 10); } |
橋の開始部分がすでにプレイヤーが通過した部分であっても橋を描画するときに白で塗りつぶされる場合があります。ReDrawBridgeEdgeIfNeed関数はすでに通過した部分を再度緑色で塗り直します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
let bridgeNearEdgeXs = []; let bridgeNearEdgeYs = []; function ReDrawBridgeEdgeIfNeed() { ctx.fillStyle = visitedColor; // 初回は配列bridgeNearEdgeXsが空なので該当する座標を取得して格納する // 2回目以降は格納されている値を使う if(bridgeNearEdgeXs.length == 0) { for(let y=0; y<mapSourceHeight; y++) { for(let x=0; x<mapSourceWidth; x++) { if(map[y][x] == POSITION.POSITION_BRIDGE_EDGE) { bridgeNearEdgeXs.push(x); bridgeNearEdgeYs.push(y); } } } } for(let i=0; i<bridgeNearEdgeXs.length; i++) { if (isVisits[bridgeNearEdgeYs[i]][bridgeNearEdgeXs[i]]) ctx.fillRect( bridgeNearEdgeXs[i] * EXPANSION_RATE + LEFT_MARGIN, bridgeNearEdgeYs[i] * EXPANSION_RATE + TOP_MARGIN, CHARACTOR_SIZE, CHARACTOR_SIZE); } } |
上側にあるものを前面に描画する
IsInMap関数は配列の範囲外にアクセスしないようにするために事前に確認するためのものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function IsInMap(y, x) { if(x < 0) return false; if(y < 0) return false; if(x >= mapSourceWidth) return false; if(y >= mapSourceHeight) return false; return true; } |
ReDrawPlayerIfNeed関数は橋の上にプレイヤーがいる場合、再度描画することでプレイヤーが橋の下に隠れてしまわないようにするためのものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function ReDrawPlayerIfNeed() { // プレイヤーが配列の範囲内にいることを確認する if(IsInMap(playerY, playerX)) { // 橋の上と端の周辺にいる場合はプレイヤーを再描画する if ( (map[playerY][playerX] == POSITION.POSITION_BRIDGE_EDGE || map[playerY][playerX] == POSITION.POSITION_BRIDGE_NEAR_EDGE || map[playerY][playerX] == POSITION.POSITION_BRIDGE_NS || map[playerY][playerX] == POSITION.POSITION_BRIDGE_WE)) { DrawPlayer(); } // プレイヤーが橋とそうでない部分の交点にいる場合は // 南北にかかる橋の上を移動している場合(移動方向が北または南)、 // または東西にかかる橋の上を移動している場合(移動方向が東または西)のみ再描画する if(map[playerY][playerX] == POSITION.POSITION_BRIDGE_NS_CROSS && (playerDirect == Direct.Up || playerDirect == Direct.Down)) DrawPlayer(); if(map[playerY][playerX] == POSITION.POSITION_BRIDGE_WE_CROSS && (playerDirect == Direct.Left || playerDirect == Direct.Right)) DrawPlayer(); } } |
Draw関数は上記の描画関数を呼び出して描画処理をおこないます。関数を呼び出す順番に注意します。下にあるものから順に描画処理をおこないます。
1 2 3 4 5 6 7 8 9 10 |
function Draw() { DrawClear(); DrawRoad(); DrawPlayer(); DrawBridgeNS(); DrawBridgeWE(); ReDrawBridgeEdgeIfNeed(); ReDrawPlayerIfNeed(); } |
プレイヤーの移動処理
キーがおされるとフラグをセットして移動できる方向のフラグがセットされているときだけ方向転換の処理をおこないます。基本的にプレイヤーは立ち止まることはできません。壁にぶつかって前進できない場合はその場に停止します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
let isKeyUp = false; let isKeyDown = false; let isKeyLeft = false; let isKeyRight = false; document.onkeydown = function(e){ if(e.key == 'ArrowRight') isKeyRight = true; if(e.key == 'ArrowLeft') isKeyLeft = true; if(e.key == 'ArrowUp') isKeyUp = true; if(e.key == 'ArrowDown') isKeyDown = true; if(e.key == 's') GameStart(); } document.onkeyup = function(e){ if(e.key == 'ArrowRight') isKeyRight = false; if(e.key == 'ArrowLeft') isKeyLeft = false; if(e.key == 'ArrowUp') isKeyUp = false; if(e.key == 'ArrowDown') isKeyDown = false; } |
プレイヤーの移動方向を設定する
SetPlayerDirect関数はフラグの状態とプレイヤーの座標をしらべてプレイヤーの移動方向を取得して、変数 playerDirectにセットします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
let playerDirect = Direct.Stop; function SetPlayerDirect() { // 橋の上または下では直進しかできない if (IsInMap(playerY, playerX) && map[playerY][playerX] != POSITION.POSITION_BRIDGE_NS_CROSS && map[playerY][playerX] != POSITION.POSITION_BRIDGE_WE_CROSS) { if (isKeyUp) { if (map[playerY - 1][playerX] != POSITION.POSITION_NONE) playerDirect = Direct.Up; } if (isKeyDown) { if (map[playerY + 1][playerX] != POSITION.POSITION_NONE) playerDirect = Direct.Down; } if (isKeyLeft) { if (map[playerY][playerX - 1] != POSITION.POSITION_NONE) playerDirect = Direct.Left; } if (isKeyRight) { if (map[playerY][playerX + 1] != POSITION.POSITION_NONE) playerDirect = Direct.Right; } } } |
移動処理
MovePlayer関数はplayerDirectに格納されているデータに従ってプレイヤーを移動させます。通路の端に来たら反対側にワープさせます。またスコア加算の処理もおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
let eatCount = 0; let score = 0; function MovePlayer() { SetPlayerDirect(); // 移動処理 if (playerDirect == Direct.Up) { if (playerY - 1 >= 0 && map[playerY - 1][playerX] > 0) playerY--; // 画面端に来たらワープ処理 if (playerY - 1 == -1) { isVisits[playerY][playerX] = true; playerY = mapSourceHeight - 1; } } if (playerDirect == Direct.Down) { if (map[playerY + 1][playerX] > 0) playerY++; if (playerY + 1 == mapSourceHeight) { isVisits[playerY][playerX] = true; playerY = 0; } } if (playerDirect == Direct.Left) { if (map[playerY][playerX - 1] > 0) playerX--; if (playerX - 1 == -1) { isVisits[playerY][playerX] = true; playerX = mapSourceWidth - 1; } } if (playerDirect == Direct.Right) { if (map[playerY][playerX + 1] > 0) playerX++; if (playerX + 1 == mapSourceWidth) { isVisits[playerY][playerX] = true; playerX = 0; } } // これまで通過していない座標を通過したら加点する。ただし加点の頻度を下げるため4回に1回とする if(!isVisits[playerY][playerX]) { isVisits[playerY][playerX] = true; eatCount++; if(eatCount % 4 == 0) score += 10; } } |
効果音とBGMの再生
効果音とBGMを再生する処理を示します。ゲームが開始されたらBGMを再生し、ゲームオーバーになったら停止します。最後まで再生されたかどうかはisPlayingBgmフラグの状態で調べます。再生が完了したらisPlayingBgmはfalseになるので、毎回ゲーム中かどうかしらべて、ゲーム中であるにもかかわらずisPlayingBgmフラグがfalseになっていたらBGMの再生が完了したとみなしてもう一度最初から再生します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
let hitSound; let deadSound; let rollerSound; let stageClearSound; let gameOverSound; let bgm; let isPlayingRollerSound = false; let isPlayingBgm = false; // 効果音とBGMに関する変数を初期化する function InitSound() { hitSound = new Audio('./hit.mp3'); deadSound = new Audio('./dead.mp3'); gameOverSound = new Audio('./gameover.mp3'); stageClearSound = new Audio('./clear.mp3'); rollerSound = new Audio('./roller.mp3'); rollerSound.addEventListener("ended", function () { isPlayingRollerSound = false; }, false); bgm = new Audio('./bgm.mp3'); bgm.addEventListener("ended", function () { isPlayingBgm = false; }, false); } |
PlayBGMIfNeed関数は、もしゲーム中であるにもかかわらずisPlayingBgmがfalseならば、もう一度再生する処理をおこなうためのものです。
1 2 3 4 5 6 7 8 9 10 11 12 |
function PlayBGMIfNeed() { if(isGaming) { if(!isPlayingBgm) { isPlayingBgm = true; bgm.currentTime = 0; bgm.play(); } } } |
StopBGM関数はBGMを止めます。
1 2 3 4 5 6 |
function StopBGM() { isPlayingBgm = false; bgm.currentTime = 0; bgm.pause(); } |
ゲーム開始と更新処理
InitStage関数はゲーム開始時やステージクリア時にステージを初期化するためのものです。
1 2 3 4 5 6 7 |
function InitStage() { ClearVisits(); InitPlayerPosition(); playerDirect = Direct.Stop; } |
GameStart関数はもじどおりゲームを開始するときに呼び出されます。
1 2 3 4 5 6 7 8 9 10 |
let isGaming = false; function GameStart() { if(!isGaming) isGaming = true; InitStage(); StartTimer1(interval); StartTimer2(); } |
Update関数はタイマーをつかって一定間隔で呼び出される関数です。いまはプレイヤーを移動させ描画処理をおこない、必要であればBGMを再生します。
1 2 3 4 5 6 |
function Update() { MovePlayer(); Draw(); PlayBGMIfNeed(); } |
タイマーの操作
StartTimer1関数はUpdate関数を呼び出すためのタイマーを開始するためのものです。引数を変えることでUpdate関数を呼び出す間隔を変更できるようにしています。
1 2 3 4 5 6 7 8 9 10 |
let timer1 = null; function StartTimer1(newInterval) { if(timer1 != null) clearInterval(timer1); timer1 = setInterval(() => { Update(); }, newInterval); } |
StopTimer1関数はUpdate関数の呼び出しを停止するためにタイマーを停止させるためのものです。
1 2 3 4 5 6 7 8 |
function StopTimer1() { if(timer1 != null) { clearInterval(timer1); timer1 = null; } } |
まだ作成していませんがプレイヤーがローラーを使用しているときは加速処理をおこないます。MovePlayer2関数はそのときの処理をおこないますが、いまは中身は空にしておきます。
1 2 3 |
function MovePlayer2() { } |
StartTimer2関数とStopTimer2関数は、MovePlayer2関数を呼び出すためのタイマーを開始させたり停止させるためのものです。
1 2 3 4 5 6 7 8 9 10 |
let timer2 = null; function StartTimer2() { if(timer2 != null) clearInterval(timer2); timer2 = setInterval(() => { MovePlayer2(); }, interval2); } |
1 2 3 4 5 6 7 8 |
function StopTimer2() { if(timer2 != null) { clearInterval(timer2); timer2 = null; } } |
ページが読み込まれたら初期化の処理をおこない、最初の描画処理をおこないます。Sキーがおされたらゲーム開始となります。
1 2 3 4 5 |
window.addEventListener('load', function(){ Init(); InitSound(); Draw(); }); |