ぷよぷよをつくる

ゲーム

00000000

自由落下

【HTML】index.html

<div id="stage">
<!--#stage--></div>
<div class="score">00000000</div>

    <div style="display:none">
        <img src="img/puyo_1.png" id="puyo_1" alt="">
        <img src="img/puyo_2.png" id="puyo_2" alt="">
        <img src="img/puyo_3.png" id="puyo_3" alt="">
        <img src="img/puyo_4.png" id="puyo_4" alt="">
        <img src="img/puyo_5.png" id="puyo_5" alt="">
    </div>

【JS】config.js

class Config {
  static puyoImageWidth = 40; //ぷよ画像の幅
  static puyoImageHeight = 40; //ぷよ画像の高

  static stageCols = 6; //ステージの横の個数
  static stageRows = 12; //ステージの盾の個数
  static stageBackgroundColor = '#11213b'; //ステージの背景色

    //初期状態のステージ
    static initialBoard = [
        [0,1,2,3,4,5],
        [1,2,3,4,5,0],
        [2,3,4,5,0,1],
        [0,0,0,0,0,0],
        [0,1,1,1,2,0],
        [0,4,5,5,2,0],
        [0,4,5,5,2,0],
        [0,4,3,3,3,0],
        [0,0,0,0,0,0],
        [1,1,0,0,2,2],
        [0,0,2,2,0,0],
        [4,4,0,0,5,5],
    ];

    static puyoColorMax = 5; //何色のぷよを使うか
    static fallingSpeed = 6; //自由落下のスピード
}

【JS】game.js

// 起動された時に呼ばれる関数を登録する
window.addEventListener("load", () =>{
    initialize();
        
    //ゲームループを開始する
    gameLoop();
});

function initialize(){
    //画像を準備する
    GameImage.initialize();
    //ステージを準備する
    Stage.initialize();
       
    //シーンを初期状態にセットする
    gameState = 'start';
    //フレームを初期化する
    frame = 0;
}
/*ゲームループ*/
function gameLoop(){
    switch(gameState){
        case 'start':
            //ゲーム開始直後の状態
            //最初は、もしかしたら空中にあるかもしれないぷよを自由落下させるところからスタート
            gameState = 'checkFallingPuyo';
            break;
        case 'checkFallingPuyo':
            //落ちるか胴か判定する状態
            if(Stage.checkFallingPuyo()){
                gameState = 'fallingPuyo';
            }else{
                gameState = '';
            }
            break;
        case 'fallingPuyo':
            //ぷよが自由落下しているアニメーション状態
            if(!Stage.fallPuyo()){
                gameState = '';
            }
            break;
    }
    frame++;
    setTimeout(gameLoop, 1000/60);// 1/60秒後(60fps)にもう一度呼び出す
}

【JS】stage.js

//ステージ
class Stage {
  static stageElement = null;
  static puyoBoard = null;
  static puyoCount = 0;
  static fallingPuyoInfoList = [];
  static initialize() {
    Stage.stageElement = document.getElementById("stage");
    Stage.stageElement.style.width = Config.puyoImageWidth * Config.stageCols + 'px';
    Stage.stageElement.style.height = Config.puyoImageHeight * Config.stageRows + 'px';
    Stage.stageElement.style.backgroundColor = Config.stageBackgroundColor;

    //ぷよぷよ盤を初期化する
    Stage.puyoCount = 0;
    Stage.puyoBoard = [];
    for (let y = 0; y < Config.stageRows; y++) {
      Stage.puyoBoard[y] = [];
      for (let x = 0; x < Config.stageCols; x++) {
        Stage.puyoBoard[y][x] = null;
      }
    } /* for(let y = 0; y < Config.stageRows; y++)*/

    /*もし初期状態のステージの情報があれば、その情報を元にぷよを配置する*/
    for (let y = 0; y < Config.stageRows; y++) {
      for (let x = 0; x < Config.stageCols; x++) {
        let puyoColor = 0;
        if (Config.initialBoard && Config.initialBoard[y][x]) {
          puyoColor = Config.initialBoard[y][x];
        }
        if (puyoColor >= 1 && puyoColor <= Config.puyoColorMax) {
          Stage.createPuyo(x, y, puyoColor);
        }
      } //for(let x
    } //for(let y
  } //static initialize

  //ぷよを新しく作って、画面上とぷよぷよ盤の両方にセットする
  static createPuyo(x, y, puyoColor) {
    //画像を作成し、画面上の適切な位置に配置する
    const puyoImage = GameImage.getPuyoImage(puyoColor);
    puyoImage.style.left = x * Config.puyoImageWidth + "px";
    puyoImage.style.top = y * Config.puyoImageHeight + "px";
    Stage.stageElement.appendChild(puyoImage);

    //ぷよぷよ盤に情報を保存する
    Stage.puyoBoard[y][x] = {
      puyoColor: puyoColor,
      element: puyoImage
    }
    //ぷよの総数を追加する
    Stage.puyoCount++;
  }/*createPuyo*/
  
  //ぷよぷよ盤にぷよ情報をセットする
    static setPuyoInfo(x, y, info){
        Stage.puyoBoard[y][x] = info;
    }

    //ぷよぷよ盤の情報を返す
    static getPuyoInfo(x, y){
        //左右、もしくは底の場合は、ダミーのぷよ情報を返す
        if(x < 0 || x >= Config.stageCols || y >= Config.stageRows){
            return{
                puyoColor:-1
            };
        }
        //y座標がマイナスの場合は、そこは空白扱いする
        if(y < 0){
            return null;
        }
        //それ以外の場合はぷよぷよ盤の情報をそのまま返す
        return Stage.puyoBoard[y][x];
    }

    //ぷよぷよ盤からぷよ情報を消す
    static removePuyoInfo(x,y){
        Stage.puyoBoard[y][x] = null;
    }

    //自由落下するぷよがあるかどうかをチェックする
    static checkFallingPuyo(){
        Stage.fallingPuyoInfoList = [];
        
        //下の行から上の行を見ていく
        for(let y = Config.stageRows - 2; y >= 0; y--){
            for(let x = 0; x<Config.stageCols; x++){
                const currentPuyoInfo = Stage.getPuyoInfo(x,y);
                if(!currentPuyoInfo){
                    //このマスにぷよがなければ、次
                    continue;
                }
                const belowPuyoInfo = Stage.getPuyoInfo(x, y+1);
                if(!belowPuyoInfo){
                    //下が空白なので、このぷよは落ちる
                    
                    //まず、ぷよぷよ盤からそのぷよを取り去る
                    Stage.removePuyoInfo(x,y);
                    
                    //自由落下した場合にどこまで落ちるか調べる
                    let destination = y;
                    while(!Stage.getPuyoInfo(x, destination+1)){
                        destination++;
                    }
                    //最終目的地に置く
                    Stage.setPuyoInfo(x, destination, currentPuyoInfo);
                    //「落ちるぷよ情報リスト」に「落ちるぷよ情報」を入れる
                    Stage.fallingPuyoInfoList.push({
                        element: currentPuyoInfo.element,
                        position: y * Config.puyoImageHeight,
                        destination: destination * Config.puyoImageHeight,
                        falling: true
                    });
                }
            }
        }
        return (Stage.fallingPuyoInfoList.length > 0);
    }

    //自由落下させる
    static fallPuyo(){
        let isFalling = false;
        for(const fallingPuyoInfo of Stage.fallingPuyoInfoList){
            if(!fallingPuyoInfo.falling){
                //すでに自由落下が終わっている
                continue;
            }
            //現在の画面上のY座標を取得して、それに自由落下分を追加する
            let position = fallingPuyoInfo.position;
            position += Config.fallingSpeed;
            
            if(position >= fallingPuyoInfo.destination){
                //自由落下終了
                position = fallingPuyoInfo.destination;
                fallingPuyoInfo.falling = false;
            }else{
                //まだ落下しているぷよがあることを記憶する
                isFalling = true;
            }
            //新しい位置を保存する
            fallingPuyoInfo.position = position;
            //ぷよを動かす
            fallingPuyoInfo.element.style.top = position + "px";
        }
        return isFalling;
    }
}/*class Stage*/

【JS】image.js

// ぷよ画像を管理&複製する
class GameImage{
    static puyoImageList = null;

    static initialize(){
        //ぷよ画像を準備する
        GameImage.puyoImageList = [];
        for(let i=0; i<Config.puyoColorMax; i++){
            const image = document.getElementById("puyo_" + (i+1));
            image.removeAttribute('id');
            image.width = Config.puyoImageWidth;
            image.height = Config.puyoImageHeight;
            image.style.position = "absolute";
            GameImage.puyoImageList[i] = image;
        }
    }

    //ぷよ画像を複製して返す
    static getPuyoImage(color){
        const image = GameImage.puyoImageList[color -1].cloneNode(true);
        return image;
    }
}

用語集

ピクセル
デジタル画像やゲーム画面を構成するプログラム上の最小単位
フレームレート
1秒間に何回更新されるかを示す。
ヒットボックス
キャラクターやオブジェクトの当たり判定を表す見えない範囲のこと。
衝突判定
2つの物体が衝突したかどうかを判断する処理のこと
イベントドリブン
プレイヤーが何かしたのに都度対応して処理をする方法
ポリゴン
3次元グラフィックスの基本的な構成要素
レベルデザイン
ゲーム内のステージやマップのことを指す。
スプライト
2次元グラフィックスのゲームにおける画像やアニメーションの単位。
パーティクルシステム
「パーティクル」と呼ばれる小さな粒子のような画像の組合わせ
メインループ/ゲームループ
ユーザー入力⇒状態更新⇒画面描画を繰り返すのが一般的だ
物理エンジン
物体の物理的な動きをシミュレーションをする機能を持ったライブラリ。物体の自由落下や衝突、回転や摩擦などの挙動を計算する
ゲームエンジン
ゲームを制作するために便利な機能をまとめたソフトウェア。