【Sassで作るCssミニゲーム】Cssミニゲームで複数のステージを実装する


2021/07/05
蛸壺の技術ブログ|Cssミニゲームで複数のステージを実装する

以前のブログではSassだけで考えたCssゲームのもっとも簡単な画面遷移の方法を解説していました。

今回はもうちょっと遷移させるステージを増やして、もっと発展性のあるCssミニゲームにするためのテクニックを解説していきます。


結局はjavascriptを(ちょっと)使う...

えらくこのブログで「Cssミニゲームで使うのはCssとHtmlのみ!」と強調して来ました。

しかし今回のお題である
複数のステージの画面を切り替えることを考えると、Cssだけで切り替える機能を作るのがとても複雑になってしまうため、せめて状態遷移くらいはjsスクリプトを添える程度使ったほうが圧倒的にスッキリと書けてしまいます。

そこで今回はCssミニゲームはCssだけで作る!という無駄なこだわりを捨て去り、ちょっとだけ
<script>タブに入れたスクリプト補助のある要素を取り入れることにしました。

もちろんCssだけで実装を目指す方は
Cssカウンターを使うと単純なマルチステージ遷移くらいなら作れるかも知れませんが。

マルチステージを作るスクリプトの仕組み

ではどのようなスクリプトを付け加えるかを少し解説していきます。

まずはおもむろに、以下のようなhtmlを用意しましたので、チェックボックスを全てチェックして、
ゲーム?ボタンを押してみてください。

やってみると分かりますが、画面1〜3を全てチェックした状態で
ゲームの状態 おわり!になり、つまりはチェックボックスを全てチェックするとゲーム終了と見なせます。

このhtmlの実装の中身を簡単にお見せすると以下のようなものです。

            
            <p>ゲームの状態 <span id="span1"></span></p>
<form name="form1">
    <input type="checkbox" name="stage1" value="stage1" /><span>画面1</span>
    <input type="checkbox" name="stage2" value="stage2" /><span>画面2</span>
    <input type="checkbox" name="stage3" value="stage3" /><span>画面3</span>
</form>
<input type="button" value="ゲーム?" onclick="checkGameState()" />
<script>
    function checkGameState() {
        const s1 = document.form1.stage1;
        const s2 = document.form1.stage2;
        const s3 = document.form1.stage3;
        if (s1.checked && s2.checked && s3.checked) {
            document.getElementById("span1").textContent = 'おわり!'
        } else {
            document.getElementById("span1").textContent = ''
        };
    }
</script>
        
仕組みは簡単で、スクリプトの中でチェックボックスタイプに指定したinput要素の持つプロパティcheckedを利用すると、html要素の状態が制御できるようになります。

順序のあるマルチステージを作る

次に画面遷移に見立て画面1 > 画面2 > 画面3 > 終わりと誘導できるようにスクリプトを変えてみましょう。以下にまずプログラム例を示します。

今回のプログラムは"画面"の文字をクリックしていくと、状態が遷移して最後にリセットして元に戻るようにしています。

このプログラムの中身は以下のようなhtmlとscssで作成しました。

            
            <form id="form1">
    <p>ゲームの状態: <span id="span1"></span></p>
    <input type="reset" id="reset">
    <input type="checkbox" id="stage1"/>
    <input type="checkbox" id="stage2"/>
    <input type="checkbox" id="stage3"/>
    <div id="gs1"><label for="stage1">画面1</label></div>
    <div id="gs2"><label for="stage2">画面2</label></div>
    <div id="gs3"><label for="stage3">画面3</label></div>
    <div id="gsr"><label for="reset">リセットしてニューゲーム</label></div>
</form>
<script>
    document.getElementById("stage3").addEventListener("change", (e) => {
        const s1 = document.getElementById("stage1");
        const s2 = document.getElementById("stage2");
        const s3 = document.getElementById("stage3");
        if (s1.checked && s2.checked && s3.checked) {
            document.getElementById("span1").textContent = 'おわり!';
        };
    });
    document.getElementById("reset").addEventListener("click", (e) => {
        document.getElementById("span1").textContent = '';
    });
</script>
        
Cssデザイン部分:

            
            #form1 {
    input { display: none; }
    #gs1 {display: block}
    #gs2 {display: none}
    #gs3 {display: none}
    #gsr {display: none}
    #stage1:checked ~ {
        #gs1 {display: none}
        #gs2 {display: block}
    }
    #stage2:checked ~ {
        #gs2 {display: none}
        #gs3 {display: block}
    }
    #stage3:checked ~ {
        #gs3 {display: none}
        #gsr {display: block}
    }
}
        
基本的には前の回で説明したinput-label要素の組み合わせで画面遷移を行うテクニックを使っているのは同じです。

前回との違いはスクリプト部分を挟んで処理をしている箇所だけです。

            
            //....
document.getElementById("stage3").addEventListener("change", (e) => {
    const s1 = document.getElementById("stage1");
    const s2 = document.getElementById("stage2");
    const s3 = document.getElementById("stage3");
    if (s1.checked && s2.checked && s3.checked) {
        document.getElementById("span1").textContent = 'おわり!';
    };
});
document.getElementById("reset").addEventListener("click", (e) => {
    document.getElementById("span1").textContent = '';
});
//....
        
ここでは最終画面にあたるstage3にイベントリスナーを仕込んで、stage3がクリックされる(つまりステージがクリアされる条件が真になる)と、ゲーム完了の処理をここで定義することができます。

また
reset要素がチェックされる時もイベントリスナーを仕込んで、再初期化の処理を定義します。form要素の中身は何もせずともリセットされますが、他の要素も再初期化する必要がある場合にはこの中で処理を記述するようにします。


複数画面遷移のあるCssミニゲームの実装例

では最後に先程の内容の応用的で、前回のプログラムをすこしだけ改造してみましょう。

このプログラムでhtml部分は以下のようになっています。

            
            <form id="game-wrapper">
    <p class="game-header">ゲームの状態: <span id="gamestate">さあゲェムを始めましょう</span></p>
    <input type="reset" id="reset"/>
    <input type="checkbox" id="start"/>
    <input type="checkbox" id="stage1"/>
    <input type="checkbox" id="stage2"/>
    <input type="checkbox" id="stage3"/>
    <div id="stage---op" class="stage stage--op">
        <label for="start">クリックしてゲェム開始!</label>
    </div>
    <div id="stage---1" class="stage stage--main stage--main--1">
        <label for="stage1">ステージ1です!クリックしてください。</label>
    </div>
    <div id="stage---2" class="stage stage--main stage--main--2">
        <label for="stage2">ステージ2です!!クリックしてください。</label>
    </div>
    <div id="stage---3" class="stage stage--main stage--main--3">
        <label for="stage3">最後のステージです!!!クリックしてください。</label>
    </div>
    <div id="stage---reset" class="stage stage--end">
        <label for="reset">リセットしてニューゲェム</label>
    </div>
</form>
<script>
    document.getElementById("start").addEventListener("change", (e) => {
        document.getElementById("gamestate").textContent = 'ゲェムプレイ中!';
    });
    document.getElementById("stage3").addEventListener("change", (e) => {
        const ss = document.getElementById("start");
        const s1 = document.getElementById("stage1");
        const s2 = document.getElementById("stage2");
        const s3 = document.getElementById("stage3");
        if (ss.checked && s1.checked && s2.checked && s3.checked) {
            document.getElementById("gamestate").textContent = 'ゲェムクリアしました!';
        };
    });
    document.getElementById("reset").addEventListener("click", (e) => {
        document.getElementById("gamestate").textContent = 'さあゲェムを始めましょう';
    });
</script>
        
そしてスタイルとして適用するScssは以下のようになります。

            
            #game-wrapper {
    width: 100%;
    height: 300px;
    background-color: darkgray;
    box-sizing: border-box;
    position:relative;
    input { display: none; }
    .game-header {
        position: absolute;
        top: 0;
        left: 0;
        background: #220c0c;
        color: #039e03;
        z-index: 1;
        margin: 0;
        font-size: 22px;
        padding: 6px 8px 6px 8px;
        border-radius: 0 0 25px 0;
    }
    .stage {
        position: absolute;
        display: block;
        width: 100%;
        height: 100%;
        font-size: 24px;
        &--op {
            display: flex;
            align-items: center;
            justify-content: center;
            label {
                display: block;
                flex: 0 0 auto;
                margin: 0 auto;
            }
        }
        &--main {
            display: flex;
            align-items: center;
            justify-content: center;
            label {
                flex: 0 0 auto;
                margin: 0 auto;
            }
            &--1 {
                color: blue;
                font-size: 18px;
            }
            &--2 {
                color: rgb(99, 231, 169);
                font-size: 22px;
            }
            &--3 {
                color: rgb(235, 212, 85);
                font-size: 24px;
            }
        }
        &--end {
            display: flex;
            align-items: center;
            justify-content: center;
            background: darkgray;
            label {
                flex: 0 0 auto;
                margin: 0 auto;
                color: white;
            }
        }
    }
    #stage {
        &---op {display: flex}
        &---1,&---2,&---3,&---reset {display: none}
    }
    #start:checked ~ {
        #stage---op {display: none}
        #stage---1 {display: flex}
    }
    #stage1:checked ~ {
        #stage---1 {display: none}
        #stage---2 {display: flex}
    }
    #stage2:checked ~ {
        #stage---2 {display: none}
        #stage---3 {display: flex}
    }
    #stage3:checked ~ {
        #stage---3 {display: none}
        #stage---reset {display: flex}
    }
}
        
Sassを使うと各ステージごとのスタイリングの記述が構造化できて、Cssミニゲームのような複雑なhtmlプログラムでも比較的人間がリソースを分かりやすく整理しながら開発できることは大きなメリットです。


まとめ

今回はCssミニゲームを作る上で必要となるであろうゲーム状態を保持・遷移させるために添えるちょっとしたjavascriptタグの話をしてみました。

各html要素の保持する値や状態を管理するのが苦手なCssをJavascriptで必要最低限のサポートをさせることで、今回のテクニックで更によりゲームらしいことが簡単に実現できることが理解していただけたかと思います。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。