こんにちは!シュウです。
今回は「懐かしいゲームをWeb技術で再現する」という企画をしてみたいと思っています。
アーケードゲームとして登場したスネークゲームは、70年代に誕生してから大きな人気を集めてきました。
私が初めてスネークゲームを知ったのは、中国でアンドロイド搭載の携帯への利用が広まってきた2009年頃でした。
スネークゲームと言ったら、壁や自身にぶつからずに、蛇を操作して餌を食べ続けるゲームです。
餌を食べた分で蛇の体も長くなります。長くなればなるほど難易度が高くなります。
では、今回は自分なりに、HTML5とJavaScirptを使ってスネークゲームを作成してみようと思います。
まずは、ゲームのステージです。どのタグを利用したら一番適切でしょうか。
私は今回テーブルタグを選びました。
その理由は、テーブルは「tr」タグを使って行を操作できるし、「td」タグを使って列の操作も簡単にできるからです。蛇の位置を特定しやすいメリットがあります。
それに、ゲームステージの拡張と縮小も、JavaScriptを触らず、HTMLだけで対応できます。以上のことからテーブルタグを使うのがベストだと考えました。
では、蛇はどのように作成するのでしょうか。
私は蛇を一つの配列として考えました。
以下は私が定義した蛇の初期状態です。
1 |
let snake = [[1,1],[1,2],[1,3]]; |
二次元で並ぶ配列の数は、蛇の長さになります。配列を追加するたびに、蛇が長くなります。
三次元の配列に保存する2つの値は、蛇のいる場所を示す座標です。
1番目の数は行で、2番目の数は列です。
1 2 3 4 5 6 |
//座標を取得し、目標のセルに「snake」クラスを付けます。 for(let i=0;i<snake.length;i++){ let tr = snake[i][0]; let td = snake[i][1]; $(`tr:nth-of-type(${tr})>td:nth-of-type(${td})`).addClass("snake"); } |
では、どうすれば、蛇を動かせるでしょうか。
蛇となる3つのセルを1個ずつ、次のセルにずらせば、蛇を一歩動かしたことになるのではないかと考えました。
ただし、この考え方だと、パソコンに大量な作業量を与えてしまう可能性があります。
3つのセルを占める蛇には、1歩につき3回繰り返しが必要です。300個のセルを占める蛇は1歩に300回も繰り返さないといけません。
そこで、私は3個のセルを一個ずつずらせる動作を図解にしてみました。すると実際に変わったのは、最初と最後のセルだけではないか?と気づきました。真ん中のセルを動かす必要はありません。
1 2 3 4 5 6 7 8 9 10 |
//最初と最後のセルの座標を取得します。 let start = snake.shift(); let end = snake[snake.length-1]; //新しいセルの座標を保存する配列を作ります。 let new_snake; new_snake = [end[0],end[1]+1]; //新しい座標の配列を追加します。 snake.push(new_snake); |
蛇の動き方向はキーボートの上下左右の矢印ボタンを押すタイミングで実行します。
1 2 3 |
document.addEventListener('keydown',(event)=>{ //命令 } |
keydownイベントで押したキーを取得します。
方向ごとに分岐処理を作って、以下のように新しいセルの座標を与えます。
1 2 3 4 5 6 7 8 |
//上 new_snake = [end[0]+1,end[1]]; //下 new_snake = [end[0]-1,end[1]]; //左 new_snake = [end[0],end[1]-1]; //右 new_snake = [end[0],end[1]+1]; |
餌を置く場所は蛇が占めるセル以外の任意の場所となります。言い換えると、「snake」クラスが付いているセル以外なら、全部OKです。
1 2 3 4 5 |
//「snake」クラスが付いているセルを取得します。 let _vacant = $("td:not(.snake)"); // ランダムでセルを抽出し、foodクラスを付けます。 let random = Math.floor(Math.random()*_vacant.length); $(_vacant[random]).addClass("food"); |
餌を置くタイミングといえば、今ある餌が蛇に食べられた時点です。
蛇を一歩動かす処理を行う時、セルに餌のクラス「food」が付いているかどうかを先に判断します。
1 |
if(_new_snake.hasClass("food")) |
もし付いていれば、餌を食べる処理に入ります。
1 2 3 4 |
//foodクラスを外し、snakeクラスを付けます _new_snake.removeClass("food"); _new_snake.addClass("snake"); snake.push(new_snake); |
スネークゲームでは、蛇が周囲の壁や自身にぶつかると、ゲームオーバーになります
動く先のセルに「snake」クラスが付いているかどうかを判断します。
1 |
_new_snake.hasClass("snake"); |
行(tr)を示すend[0]、列(td)を示すend[1]の最小値または最大値となるかどうかを判断します。
1 2 3 4 |
end[0]>tr_num end[0]<1 end[1]>td_num end[1]<1 |
今回このブログを書くために、スネークゲームを検索してみました。
少し変わったスネークゲームもありました。ステージのフチだけではなく、ステージの中にも障害物を設置し、難易度を高めていました。
それをきっかけに、どのように新しい要素を取り入れ、ゲームを少し変え、より面白くさせるかを私も考えてみました。
餌を食べるたびに、蛇の体が一個分長くなります。逆に、食べると、長さが減る餌を設置したら、面白いのではないかと考えました。
体を長くさせることを目指しているゲームなのに、短くなる心配がでてきます。
どのようにマイナスとなる餌を避け、プラスとなる餌を取るかを考えないといけません。
では、どのように実装すると良いでしょうか。
1、タイミングを設定し、ランダムでマイナス餌(クラス名:mimus)を作ります。
2、食べた餌がマイナス餌のクラス名が付いていれは、マイナス餌の分岐処理に入ります。
1 2 3 4 5 6 7 8 |
//snakeのラスト節の座標から、セルを取得し、クラス名を取り下げる let _now_snake = snake[snake.length-1]; let _now_snake_x = Number(_now_snake[0]); let _now_snake_y = Number(_now_snake[1]); $(`tr:nth-of-type(${_now_snake_x})>td:nth-of-type(${_now_snake_y})`).removeClass("snake"); //蛇配列の最後の要素を削除する snake.pop(); |
特に長い蛇を操作すると、タイミングを逃したら、蛇を回すだけで時間がかかります。「時間」にも何かできることがあるのではないかと思いつきました。
餌を取るのに、制限時間をつけます。制限時間を超えたら、餌から障害物に変わります。
そうすると、障害物を増やさないために、どのように餌を取るかを考える楽しみも増えました。
1、ゲーム終了を判断する分岐文に「障害物(クラス名:obstacle)が付いているtd」という条件を加えます。
2、餌に時間制限を付けます。餌が出でくる時点から、カウントを始めます。制限時間になってまだ食べられていなかったら、餌(クラス名:food)のクラス名を取り下げ、障害物(クラス名:obstacle)のクラス名に付け直します。
1 2 3 4 |
setTimeout(() => { $(_vacant[random]).removeClass("food"); $(_vacant[random]).addClass("obstacle"); }, 制限時間(秒)); |
今回は以上です。スネークゲームをWeb技術で再現し、新しい要素を取り入れてみました。
Webサイトのリニューアルも、決してただ単に元サイトにある要素を組み直すだけではありません。
ディレクターがお客さまの要望に合わせて現状を分析し、課題を掘り出し、解決策を考えます。構成を踏まえた上で、相応しい新要素を提案する場合もあります。
クリエイターとしては、常に自分で考えることがとても大事だと思います。
これからも、単にある物を再現するだけで終わらず、自分で新しい要素を考えて実行することを楽しんでいきたいと思っています。