以下の内容はhttps://tosuma.hatenadiary.com/entry/2024/08/09/203830より取得しました。


Javascriptで作るオリンピックのルーレットぽい物

こんにちは、tosumaです。


今回はJavascriptのお話です。
先日、ジョーク動画で例のルーレットぽい物を作りました。

ライブラリにはenchant.jsを使っています。
enchant.jsはオープンソースHTML5向けゲームエンジンです。
公式サイトも閉鎖してGitの更新もなので実質終了したライブラリですね。
と言いつつ個人的には使いやすくて使い続けています。

Wikiはコチラ。
enchant.js - Wikipedia


今回は上記の軽く、かつ、ざっくりとソースの紹介をします。
だいたい20~30分くらいで作ったものですね。
(このブログのためにインデントつけたりは後付け)


以下が実際に動くものです。
これを動画キャプチャしてトリミングして出来上がりです。
しばらくは置いておきます。(しばらくしたら消します)
https://taytsm.com/roulette/



1|ディレクトリ構成

ディレクトリ構成はこんな感じです。

今回はイメージなど使わないのでね、まぁ適当です。笑
ライブラリ置くだけなのでフォルダ切る必要もないくらい。

2|index.html

index.htmlはこんな感じです。

bodyだけ整えてiframeでgame.htmlを読み込んでます。
index.html、game.html、jsで定義した領域の幅と高さは合わせておくのがポイントですかね。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>ルーレット</title>
        <style type="text/css">
            body {
                background-color: #1e90ff;
                color: #000000;
            }
            .center {
                width: 700px;
                height: 780px;
                margin-right: auto;
                margin-left: auto;
            }
        </style>
    </head>
    <body>
        <div class="center">
            <iframe src="game.html" width="700" height="780" scrolling="no" marginwidth="0" marginheight="0" frameborder="0" >
            </iframe>
        </div>
    </body>
</html>

3|game.html

game.htmlはこんな感じです。

こちらがメインの処理になります、普段は「<script>~</script>」の部分は別ファイルにして、そこの中だけ差し替えています。
enchant.jsの仕組みを使うためjsフォルダのenchant.min.jsを参照してます。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <script src="js/enchant.min.js"></script>
        <script>
        enchant();
        enchant.ENV.USE_TOUCH_TO_START_SCENE = false;

        window.onload = function () {

            //画面サイズ
            const game = new Game(700, 780);
            game.fps = 50;

            //ロード後 呼出
            game.onload = function () {

                //-----------------------------------------------------
                // シーン
                //-----------------------------------------------------

                //メインシーン
                const mainScene = new Scene();
                game.pushScene(mainScene);
                mainScene.backgroundColor = "#1e90ff";

                //-----------------------------------------------------
                // 共通変数
                //-----------------------------------------------------

                var counter_main = 0;    //メインカウンター
                var counter_sub = 0;     //サブカウンター
                var speed = 1;           //回転速度
                var y_position = 200;    //開始位置
                var soubi = new Array(); //装備配列

                //-----------------------------------------------------
                // タイトル
                //-----------------------------------------------------

                //タイトル1
                const titleText_01 = new Label();
                titleText_01.font = "bold 40px Meiryo";
                titleText_01.color = '#fff';
                titleText_01.width = 700;
                titleText_01.moveTo(100, 310);
                mainScene.addChild(titleText_01);
                titleText_01.text = 'JUST BEFORE THE 10th';

                //タイトル2
                const titleText_02 = new Label();
                titleText_02.font = "bold 40px Meiryo";
                titleText_02.color = '#fff';
                titleText_02.width = 700;
                titleText_02.moveTo(10,370);
                mainScene.addChild(titleText_02);
                titleText_02.text = 'ANNIVERSARY OF ITS RELEASE';

                //タイトル3
                const titleText_03 = new Label();
                titleText_03.font = "bold 40px Meiryo";
                titleText_03.color = '#fff';
                titleText_03.width = 700;
                titleText_03.moveTo(65,430);
                mainScene.addChild(titleText_03);
                titleText_03.text = 'COUNTDOWN RELIC DRAW';

                //-----------------------------------------------------
                // 装備
                //-----------------------------------------------------

                //閃技(★7)
                soubi[0] = new Label();
                soubi[0].font = "bold 50px Meiryo";
                soubi[0].color = '#fff';
                soubi[0].width = 700;
                soubi[0].moveTo(220,y_position);
                soubi[0].opacity = 0.0;
                mainScene.addChild(soubi[0]);
                soubi[0].text = '閃技(★7)';

                y_position = y_position + 70;

                //究極神技
                soubi[1] = new Label();
                soubi[1].font = "bold 50px Meiryo";
                soubi[1].color = '#00ffff';
                soubi[1].width = 700;
                soubi[1].moveTo(235,y_position);
                soubi[1].opacity = 0.0;
                mainScene.addChild(soubi[1]);
                soubi[1].text = '究極神技';

                y_position = y_position + 70;

                //クリスタル神技
                soubi[2] = new Label();
                soubi[2].font = "bold 50px Meiryo";
                soubi[2].color = '#fff';
                soubi[2].width = 700;
                soubi[2].moveTo(160,y_position);
                soubi[2].opacity = 0.0;
                mainScene.addChild(soubi[2]);
                soubi[2].text = 'クリスタル神技';

                y_position = y_position + 70;

                //アクセル神技
                soubi[3] = new Label();
                soubi[3].font = "bold 50px Meiryo";
                soubi[3].color = '#00ffff';
                soubi[3].width = 700;
                soubi[3].moveTo(190,y_position);
                soubi[3].opacity = 0.0;
                mainScene.addChild(soubi[3]);
                soubi[3].text = 'アクセル神技';

                y_position = y_position + 70;

                //オーバーフロー神技
                soubi[4] = new Label();
                soubi[4].font = "bold 50px Meiryo";
                soubi[4].color = '#fff';
                soubi[4].width = 700;
                soubi[4].moveTo(120,y_position);
                soubi[4].opacity = 0.0;
                mainScene.addChild(soubi[4]);
                soubi[4].text = 'オーバーフロー神技';

                y_position = y_position + 70;

                //シンクロ奥義
                soubi[5] = new Label();
                soubi[5].font = "bold 50px Meiryo";
                soubi[5].color = '#00ffff';
                soubi[5].width = 700;
                soubi[5].moveTo(190,y_position);
                soubi[5].opacity = 0.0;
                mainScene.addChild(soubi[5]);
                soubi[5].text = 'シンクロ奥義';

                y_position = y_position + 70;

                //真奥義
                soubi[6] = new Label();
                soubi[6].font = "bold 50px Meiryo";
                soubi[6].color = '#fff';
                soubi[6].width = 700;
                soubi[6].moveTo(260,y_position);
                soubi[6].opacity = 0.0;
                mainScene.addChild(soubi[6]);
                soubi[6].text = '真奥義';

                y_position = y_position + 70;

                //デュアル覚醒奥義
                soubi[7] = new Label();
                soubi[7].font = "bold 50px Meiryo";
                soubi[7].color = '#00ffff';
                soubi[7].width = 700;
                soubi[7].moveTo(135,y_position);
                soubi[7].opacity = 0.0;
                mainScene.addChild(soubi[7]);
                soubi[7].text = 'デュアル覚醒奥義';

                //透明のオブジェクト(上)
                var coverSquare_1_s = new Sprite(700,200);
                var coverSquare_1 = new Surface(700,200);
                coverSquare_1.context.beginPath();
                coverSquare_1.context.fillStyle = 'rgba(30,144,255,1.0)';
                coverSquare_1.context.fillRect(0,0,700,200);
                coverSquare_1_s.image = coverSquare_1;
                coverSquare_1_s.moveTo(0,0);
                coverSquare_1_s.opacity = 1.0;
                mainScene.addChild(coverSquare_1_s);

                //透明のオブジェクト(下)
                var coverSquare_2_s = new Sprite(700,200);
                var coverSquare_2 = new Surface(700,200);
                coverSquare_2.context.beginPath();
                coverSquare_2.context.fillStyle = 'rgba(30,144,255,1.0)';
                coverSquare_2.context.fillRect(0,0,700,200);
                coverSquare_2_s.image = coverSquare_2;
                coverSquare_2_s.moveTo(0,585);
                coverSquare_2_s.opacity = 1.0;
                mainScene.addChild(coverSquare_2_s);

                //ここからメイン処理
                mainScene.onenterframe = function () {
                
                  //カウンターをインクリメント
                  counter_main += 1;

                  //タイトルを非表示
                  if ( counter_main > 50 && counter_main < 150 ) {
                      if ( titleText_01.opacity > 0.01 ) {
                          titleText_01.opacity -= 0.01;
                          titleText_02.opacity -= 0.01;
                          titleText_03.opacity -= 0.01;
                      } else {
                          titleText_01.opacity = 0.0;
                          titleText_02.opacity = 0.0;
                          titleText_03.opacity = 0.0;
                      }
                  }

                  //ルーレットの要素を表示
                  if ( counter_main >= 150 && counter_main < 250 ) {
                      for (let i = 0; i <= 7; i++) {
                          if ( soubi[0].opacity < 0.99 ) {
                              soubi[i].opacity += 0.01;
                          } else {
                              soubi[i].opacity = 1.0;
                          }
                      }
                  }

                  //ルーレット回転(上方向)
                  if ( counter_main >= 250 && counter_main < 290 ) {
                      for (let i = 0; i <= 7; i++) {
                          soubi[i].y -= 1;
                      }
                  }

                  //ルーレット回転(下方向)徐々に速度アップ
                  if ( counter_main >= 290 && counter_main < 390 ) {
                      counter_sub += 1;
                      for (let i = 0; i <= 7; i++) {
                          soubi[i].y += speed;
                          if ( soubi[i].y >= 760 ) {
                              soubi[i].y = 200;
                          }
                      }
                      if ( counter_sub >= 10 && speed < 10 ) {
                        speed += 1;
                        counter_sub = 0;
                      } 
                  }

                  //ルーレット回転(下方向)
                  if ( counter_main >= 390 && counter_main < 700 ) {
                      for (let i = 0; i <= 7; i++) {
                          soubi[i].y += 10;
                          if ( soubi[i].y >= 760 ) {
                              if ( i==7 ) {
                                  soubi[i].y = soubi[0].y - 70;
                              } else {
                                  soubi[i].y = soubi[i+1].y - 70;
                              }
                          }
                      }
                  }

                  //ルーレット回転(下方向)徐々に速度ダウン
                  if ( counter_main >= 700 && counter_main < 910 ) {
                      counter_sub += 1;
                      for (let i = 0; i <= 7; i++) {
                          soubi[i].y += speed;
                          if ( soubi[i].y >= 760 ) {
                              if ( i==7 ) {
                                  soubi[i].y = soubi[0].y - 70;
                              } else {
                                  soubi[i].y = soubi[i+1].y - 70;
                              }
                          }
                      }
                      if ( counter_sub >= 10 && speed > 1 ) {
                        speed -= 1;
                        counter_sub = 0;
                      } 
                  }

                  //ルーレットの文字を拡大
                  if ( counter_main >= 910 && counter_main < 955 ) {
                      soubi[4].x += 1.2;
                      soubi[4].y -= 0.2;
                      soubi[4].scaleX += 0.01;
                      soubi[4].scaleY += 0.01;
                  }

                };

            };

            game.start();
        };

        </script>
    </head>
</html>

こんな感じですね。

まず前提として「mainScene.onenterframe = function () {~}」の部分が延々とループされ処理が動く感じです。
あとはcounter_main を順にカウント増やしていってカウントに応じた処理を細々してる感じ。

ほんとはもっとスマートな実装方法があるとは思いますがちょっとしたものは凝らずに作るのも悪くは無いです。
コピペしてポンポンと処理増やせますしね。笑

必殺技の部分なんかはClassにしておくとホントは便利ですね。
一回だけポストするだけの即席プログラムなので平打ちで適当に作ってますが。笑


今回は細かい解説ではなく、こんな感じで作ってるよ!!ってのが少しでも伝わればいいかなと思った記事でした。
それでは皆さんも良いテクニカルライフを!!



お読み頂き有難うございました。




以上の内容はhttps://tosuma.hatenadiary.com/entry/2024/08/09/203830より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14