2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

なんちゃってブラウザゲームを作った話

Last updated at Posted at 2023-11-23

■はじめに

読んだらいい人、読んでほしい人

  • ゲーム作ってみたい方。どうやって作ったの?が気になる方
  • JavaScript触れるから、アドバイスしてやろうというお優しい方

こんなゲームを作りました↓

直線上に現れる敵をジャンプと攻撃で回避し、ハイスコアを狙うゲームです。

symple_jumping_game (1).gif

ゲーム作成のきっかけ

  • プログラムをやってみたい → 何か自分で作ってみよう! 
    ⇒何が楽しいかな → ゲーム?!
    と短絡思考で着手しました。

■やったこと大雑把に

  1. youtubeで動画を見ながら、ゲーム制作の大体の構造を学ぶ
  2. テーマ、企画を考える
  3. コーディング、プログラミング
  4. ゲームバランス整える
  5. ビルド
  6. 事後作業(github,qitta)

以下、順に詳しく説明します。

1.youtubeで動画を見ながら、ゲーム制作の大体の構造を学ぶ

勉強させていただいた動画↓

Part1~6まであります。素地はこの動画を見て作成しました。
ゲームを組み立てる機能や設計について、ソースコードを交えながら説明してくれるのが
とても分かりやすかったです。

2. テーマ、企画を考える

ゲームの内容は、直線上に現れる敵をジャンプと攻撃で回避し、ハイスコアを狙います。
ちなみに、登場するプレイヤー、敵、味方は、私の家族をモチーフにしています(笑)

player…  母親
enemy1… 私 (眠そうなキャラ)
enemy2… 弟 (足が速いので、速いスピードでプレイヤー追い越し邪魔します)
enemy3… 父親 (よく寝ているので、母親に布団をもぎ取られます。布団が吹っ飛んだ~)
friend…  妹 (誰に対しても愛想がよく、優しいのでプレイヤーの体力を回復させます)

3.コーディング、プログラミング(ソースコードの備忘録)

全体に関してはGitHub上にアップロードしています。
ここでは一部のソースコードのみ紹介します。

ループ処理

setIntervalを用いて、16msに1回(60fps)update関数、draw関数が呼び出されるようにしています。また、gameoverになった際は、clearIntervalでループを止めています。

function fps(){
  var fps = setInterval(()=> {
    update();
    draw();
    if(scene == Scenes.GameOver){
      console.log('stop');
      clearInterval(fps);
    }
  },16);
}

キー入力

スペースでジャンプ、"F"キーで攻撃アクションが出力されるようにしています。

ジャンプ入力について、フレーム毎にy= speed + accelerationで変化を
つけました。ジャンプはy軸方向の移動かつy軸は下方向に正のため、初期値はspeedが負、accelerationが正になっています。

  // スペースキーが押されたときの処理
  if(e.key === ' '){
    player.speed = -20;
    player.acceleration =1.0;
    }

攻撃アクションは、Fキーを押下した際に、plyaerとenemy3が一定距離以内の場合に発動し、enemy3を吹き飛ばします。
具体的な処理としては、場にいるすべての敵からenemy3だけに絞りこみ、playerとの距離が攻撃のあたり判定距離以内であるかを確認します。あたり判定有の場合、enemy3のy座標がスポーン時よりかなり高い位置に再設定されます。またおまけ要素として、playerのアニメーションが0.5秒間tornade要素つきに変更されるようにします。

  // Fキーが押された時の処理
  if(e.key === 'f' || e.key === 'F'){
    for(const e of enemies){
      if(e.type == 'enemy3'){
        var diffX3 = (e.posx - player.posx)**2;
        var diffY3 = (e.posy - player.posy)**2;
        var distance3 = diffX3 + diffY3;
        var r3 = (e.r+ player.r)**2;
        var action_r = ((e.r + 150)+ player.r)**2;
        if(distance3  > r3 && distance3 <= action_r ){
          e.posy = 150;
          e.speed = 20;
          player.image.src = './image/player_tornade.png';
          setTimeout(()=>{
            player.image.src = "./image/player.png";
          },"500"); 
    }

敵の状態更新

ランダムな確率でenemy1~3とfriend1が、ランダムにスポーンされます。
point 1 必ずフレーム外から、enemy,friendがスポーンされます。
point 2 EnemyクラスからnewEnemyインスタンスを作成し、enemies配列に挿入し、敵を管理しています。

    if (Math.random() < 0.01) {
      const enemyType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
      const newEnemy = new Enemy();
      newEnemy.type = enemyType.type;
      newEnemy.image = new Image();
      newEnemy.image.src = enemyType.src;
      if(newEnemy.type == 'enemy2'||newEnemy.type == 'enemy3'||newEnemy.type == 'friend1'){
      newEnemy.posx = Math.floor(Math.random() *1081)+1080;
      }else if(newEnemy.type == 'enemy1'){
      newEnemy.posx == 0;
      }
      newEnemy.posy = 600;
      newEnemy.r = enemyType.r;
      newEnemy.speed = enemyType.speed;
      newEnemy.acceleration = enemyType.acceleration;
      newEnemy.clash_flg = enemyType.clash_flg;

      // 新しい敵をenemies配列に追加
      enemies.push(newEnemy);
    }
  
    // 範囲外の敵を除外
    enemies = enemies.filter((e) => (e.posx <= 1080) || (e.posx >= 0));

当たり判定

オブジェクトを円と仮定し、中心間の距離と半径の和の差の大小で衝突判定をおこないました。
player, friend, enemyにそれぞれ半径(r)を持たせ、三平方の定理を用いて距離を求めています。またオブジェクトに衝突フラグ(clash_flg)を持たせることで、スコア加算時に確認したり、複数回当たり判定が行われるのを防いでいます。

    for(const e of enemies){
      var diffX1 = (e.posx - player.posx)**2;
      var diffY1 = (e.posy - player.posy)**2;
      var distance1 = diffX1 + diffY1;
      var r1 = (e.r+ player.r)**2;

      if(player.heart < 3 && distance1 <= r1 && e.clash_flg == false && e.type == 'friend1'){
        player.heart += 1;
        e.clash_flg = true;
        console.log('heal');
      }else if(distance1 <= r1 && e.clash_flg == false && (e.type == 'enemy1' ||e.type == 'enemy2'||e.type == 'enemy3')){
          player.heart -= 1;
          e.clash_flg = true;
          console.log('damage');
        }
      };

htmlとcss

主にシーンごとに画面上に表示する要素を入れ替えるために、使用しました。
createElement  … htmlタグを作る
setAttribute  … idをセットする
appendChild   … .htmlファイルに挿入する 

const button_area = document.createElement("div");
  button_area.setAttribute("id",button_area);
  var button_area_style = button_area.style;
  var button_area_styles = {
    display: 'flex',
    position: 'fixed',
    width: '1080px',
  }
  for(var style in button_area_styles){
    button_area_style[style] = button_area_styles[style];
  }
  document.body.appendChild(button_area);

4. ゲームバランス整える

ここは、確立やリスポーン地点の倍率などをよしなに調整しました。

5. ビルド

ふりーむに投稿しました。現在審査中です。
クリエイター登録して、およそ15分ほどでファイルのアップロードも可能でした!

6. 事後作業(github,qitta)

余談)githubに挙げるのにめちゃ苦労した

gitのインストールは事前にしていたが、リポジトリの意味が分からず、苦戦・・・
いろいろ調べた結果、SSHの設定ができてなかったために、commitでエラーが出てしまっていたという点でした。

■最後に

今回は、私が作っていて楽しい自己満足のゲームができてしまいました(笑)
アルゴリズムの基本の "K!" は知れたのではないかと満足です。
拙い説明、ソースコードのため、間違いや改善点は随時修正していこうと思います。
ちなみに、今回自己満足だけでなくQittaで記事を投稿しようと思った理由として、

qiitaに残す理由

  • 今後の転職を見据えて自分のプログラムを磨きたい
  • 言語化して忘れないようにしたい
  • 自分の今の立ち位置を明確化する

がありました!引き続き精進していこうと思います。
以上、最後までお読みいただきありがとうございます。

■参考

2
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?