【リアイム】


タップ(クリック)で消す



よみこみ中…








閉じる
もくじ

リアイム

アウトプット&知識の共有

JavaScript キャラクターがサイト内を動き回るプログラムを作ってみた 【はてなブログ】

f:id:a1t2s2u2:20211230135558p:plain


当ブログ内の『おばけが駆け回る』プログラムの制作過程をお届けします。完成は現在サイト内に出没しているおばけをご覧ください。

表示されない場合、もしかしたらおばけは別次元にいるかもしれません。時間が経ったら戻ってきます。




設計

f:id:a1t2s2u2:20211228150640j:plain

僕は通常、設計をやることがないのであんまり丁寧なものではありません。どんなものを作りたいかという完成像を思い浮かべることができたらそれで大丈夫です。


概要

記事を読むときって一人寂しいかもしれません。
そこで、おばけと触れ合うことのできるプログラムを作成して、ブログでの楽しい体験をお届けできるようにします。


おばけが

  • サイト内を自由に移動することができる
  • 喋れるようにする
  • 感情に応じて変形する
  • はてなスターを押すと喜ぶ

など、できるだけ多くのことを実装してみたいと思います。


おばけはたまに出没するものとします。


もしかしたら、この記事を書いたずっと後に画像の変更や、仕様の変更があるかもしれません。いや、多分あります。
開発初期段階ということでお読みください。


使用画像

f:id:a1t2s2u2:20211230110109j:plain

※ 今後はもっとたくさんの種類のおばけが登場します。ただ、絵を描くことに苦戦しているため、最初はこの『好奇心旺盛おばけ』のみでの実装とします。


動き

f:id:a1t2s2u2:20211225185532j:plain

指定したサイトのポジション(割合指定)へ向けて、ランダムで得られた時間で移動する。喋る時もランダム。

  • 邪魔にならないように
    読むことを妨害したり、タップしたいところにいてはいけない。
  • 移動
    指定したサイトのポジション(割合指定)へ向けて、ランダムで得られた時間で移動する。喋る時もランダム。
  • 喋る時
    完全にランダムにする。



【 移動の詳細 】

例えば、
  サイトの横幅(width):1000px
  縦幅(height):1000px

の場合、場所を(0.1, 0.9)指定にするとおばけは(100px, 900px)の位置に到着する。(width * 0.1, height * 0.9)として得られる。

瞬間移動だと面白くないので、ちゃんと移動しているように何度かに分けた移動にする。その分けた数をdivisionと定義して、移動時間をランダムで得るものとする。

  移動時間:2秒
  division:1000

だったら、0.002秒で1/1000すすむ。

設計で大切なこと

大きなプロジェクトでない限り、別に文字にしてどのようなものを作るかを描く必要はありませんが、イメージを抱けるようするべきです。

プログラムを書きながら、「どうしようかなぁ〜」だと結構遅くなってしまうためです。




骨組みであるhtmlを先に完成させましょう。
その後はJavaScriptで動きを足します。

cssはお好みですね。



プログラム

htmlとcss

<!--おばけ移動-->
<div id='ghost'>

  <center>
      <div onclick='ghost_click();'>
          <img src='https://cdn-ak.f.st-hatena.com/images/fotolife/a/a1t2s2u2/20211230/20211230110109.jpg' id='ghost-icon' class='ghost-img'>
      </div>
      <p id='ghost-message' class=''></p>
  </center>

  <style>
      #ghost {
          z-index: 5000;
          position: fixed;
          left: 5%;top: 5%;
          width: 200px;
          overflow: visible;
      }
      .ghost-img {
          border: 2px solid pink;
          background: white;
          border-radius: 90%;
          margin: 5px;padding: 8px;
          width: 90px;
          pointer-events: none;
      }
      .ghost-img:hover {
          border-color: skyblue;
      }
      #ghost-message {
          margin: 0;
          color: whitesmoke;
          opacity: 0;
          font-size: 90%;
          padding: 5px 10px;
          background: rgb(67, 67, 67);
          border: 2px solid whitesmoke;
          border-radius: 5%;
      }
      .ghost-message-appear {
          animation: ghost-message-appearing 2s forwards;
      }
      @keyframes ghost-message-appearing {100%{opacity: 1;}}
      .ghost-disappear {
          animation: ghost-disappearing 4s forwards;
      }
      @keyframes ghost-disappearing {100%{border-color: skyblue;opacity: 0;}}
      
  </style>
</div>

まとめると、

  • a id="ghost-message"は喋る時のふきだし
  • おばけの上にマウスが乗った場合、borderの色が変化する。
  • アニメーションのcssは喋る時用

これにJavaScriptを使って動きを実装する。

JavaScript

var ghost_status = true;
let ghost_messages = [
    '良いお年を',
    'リアイムで<br>たくさんの発見<br>をしよう!',
    'わーい',
    '寒すぎるよぉぉ',
    'こんにちは!',
    'よかったら<b>共有してね!',
];

// position指定 サイト内での割合
let ghost_positions = [
    [0.1, 0.1],
    [0.2, 0.7],
    [0.8, 0.7],
    [0.1, 0.6],
    [0.7, 0.1],
];

function ghost_message(message) {
    document.getElementById('ghost-message').classList.add('ghost-message-appear');
    document.getElementById('ghost-message').innerHTML = message;
    setTimeout(() => {
        document.getElementById('ghost-message').classList.remove('ghost-message-appear');
        document.getElementById('ghost-message').innerHTML = '';
    }, 5000);
}
// timeはms指定
function ghost_move(x, y, time) {
    // 高いほど滑らか
    let division = 1000;
    for(var i=0;i<division;++i) {
        setTimeout(() => {
            let left = document.getElementById('ghost').getBoundingClientRect().left;
            let top = document.getElementById('ghost').getBoundingClientRect().top;
            document.getElementById('ghost').style.left = left + (x - left) / division + "px";
            document.getElementById('ghost').style.top = top + (y - top) / division + "px";
        }, (time / division) * i);
        setTimeout(() => {
          document.getElementById('ghost-icon').style.border = '2px solid pink';
        }, time);
    }
}
setInterval( () => {
    if(ghost_status) { // おばけが出没状態であれば
        // しゃべっていない時に実行
        if((Math.floor( Math.random() * 4) == 0)&&(document.getElementById('ghost-message').innerHTML == '')) {
            ghost_message(ghost_messages[Math.floor( Math.random() * ghost_messages.length)]);
        }
        if(Math.floor( Math.random() * 3) == 0) {
            // 移動時間の最小最大
            let min = 1.5;let max = 3;
            let number = Math.floor( Math.random() * ghost_positions.length);
            ghost_move(
                x = window.outerWidth * ghost_positions[number][0],
                y = window.outerHeight * ghost_positions[number][1],
                time = (Math.floor( Math.random() * (max + 1 - min) ) + min) * 1000
            );
        }
        if(Math.floor( Math.random() * 15) == 0) {
            // 消滅させる if しゃべってない状態
            if(document.getElementById('ghost-message').innerHTML == '') {
                ghost_status = false;
                document.getElementById('ghost').classList.add('ghost-disappear');
                setTimeout(() => {
                    ghost_status = true;
                    document.getElementById('ghost').classList.remove('ghost-disappear');
                }, 10000);
            }
        }
    }
}, 1000);

// おばけがクリックされたら実行
function ghost_click() { // 高速移動
    // 移動時間の最小最大
    let min = 0.5;let max = 1.5;
    let number = Math.floor( Math.random() * ghost_positions.length);
    document.getElementById('ghost-icon').style.border = '2px solid skyblue';
    ghost_move(
        x = window.outerWidth * ghost_positions[number][0],
        y = window.outerHeight * ghost_positions[number][1],
        time = (Math.floor( Math.random() * (max + 1 - min) ) + min) * 1000
    );
}

要約すると

  • 1秒ごとの更新で、「喋る」「移動」はどちらも10%の確率
  • ghost_messagesの中からセリフが決まる
  • ghost_positionsから位置が決まる。
  • function ghost_message()は実際にメッセージ表示のための関数
  • 喋っているときは新しいメッセージ表示不可
  • メッセージは5秒間表示される
  • ghost_statusはおばけの出没状態を表す

jsは割と手を抜いても動いてくれるから楽ですよね笑

全体のコード

f:id:a1t2s2u2:20211223214625j:plain

<html>

    <head>
        <title>おばけの実験場</title>
    </head>

    <body>


        <!--おばけ移動-->
        <div id='ghost'>

            <center>
                <div onclick='ghost_click();'>
                    <img src='https://cdn-ak.f.st-hatena.com/images/fotolife/a/a1t2s2u2/20211230/20211230110109.jpg' id='ghost-icon' class='ghost-img'>
                </div>
                <p id='ghost-message' class=''></p>
            </center>
        
            <style>
                #ghost {
                    z-index: 5000;
                    position: fixed;
                    left: 5%;top: 5%;
                    width: 200px;
                    overflow: visible;
                }
                .ghost-img {
                    border: 2px solid pink;
                    background: white;
                    border-radius: 90%;
                    margin: 5px;padding: 8px;
                    width: 90px;
                    pointer-events: none;
                }
                .ghost-img:hover {
                    border-color: skyblue;
                }
                #ghost-message {
                    margin: 0;
                    color: whitesmoke;
                    opacity: 0;
                    font-size: 90%;
                    padding: 5px 10px;
                    background: rgb(67, 67, 67);
                    border: 2px solid whitesmoke;
                    border-radius: 5%;
                }
                .ghost-message-appear {
                    animation: ghost-message-appearing 2s forwards;
                }
                @keyframes ghost-message-appearing {100%{opacity: 1;}}
                .ghost-disappear {
                    animation: ghost-disappearing 4s forwards;
                }
                @keyframes ghost-disappearing {100%{border-color: skyblue;opacity: 0;}}
                
            </style>
            <script>
                var ghost_status = true;
                let ghost_messages = [
                    '良いお年を',
                    'リアイムで<br>たくさんの発見<br>をしよう!',
                    'わーい',
                    '寒すぎるよぉぉ',
                    'こんにちは!',
                    'よかったら<b>共有してね!',
                ];
        
                // position指定 サイト内での割合
                let ghost_positions = [
                    [0.1, 0.1],
                    [0.2, 0.7],
                    [0.8, 0.7],
                    [0.1, 0.6],
                    [0.7, 0.1],
                ];
        
                function ghost_message(message) {
                    document.getElementById('ghost-message').classList.add('ghost-message-appear');
                    document.getElementById('ghost-message').innerHTML = message;
                    setTimeout(() => {
                        document.getElementById('ghost-message').classList.remove('ghost-message-appear');
                        document.getElementById('ghost-message').innerHTML = '';
                    }, 5000);
                }
                // timeはms指定
                function ghost_move(x, y, time) {
                    // 高いほど滑らか
                    let division = 1000;
                    for(var i=0;i<division;++i) {
                        setTimeout(() => {
                            let left = document.getElementById('ghost').getBoundingClientRect().left;
                            let top = document.getElementById('ghost').getBoundingClientRect().top;
                            document.getElementById('ghost').style.left = left + (x - left) / division + "px";
                            document.getElementById('ghost').style.top = top + (y - top) / division + "px";
                        }, (time / division) * i);
                        setTimeout(() => {
                        document.getElementById('ghost-icon').style.border = '2px solid pink';
                        }, time);
                    }
                }
                setInterval( () => {
                    if(ghost_status) { // おばけが出没状態であれば
                        // しゃべっていない時に実行
                        if((Math.floor( Math.random() * 4) == 0)&&(document.getElementById('ghost-message').innerHTML == '')) {
                            ghost_message(ghost_messages[Math.floor( Math.random() * ghost_messages.length)]);
                        }
                        if(Math.floor( Math.random() * 3) == 0) {
                            // 移動時間の最小最大
                            let min = 1.5;let max = 3;
                            let number = Math.floor( Math.random() * ghost_positions.length);
                            ghost_move(
                                x = window.outerWidth * ghost_positions[number][0],
                                y = window.outerHeight * ghost_positions[number][1],
                                time = (Math.floor( Math.random() * (max + 1 - min) ) + min) * 1000
                            );
                        }
                        if(Math.floor( Math.random() * 15) == 0) {
                            // 消滅させる if しゃべってない状態
                            if(document.getElementById('ghost-message').innerHTML == '') {
                                ghost_status = false;
                                document.getElementById('ghost').classList.add('ghost-disappear');
                                setTimeout(() => {
                                    ghost_status = true;
                                    document.getElementById('ghost').classList.remove('ghost-disappear');
                                }, 10000);
                            }
                        }
                    }
                }, 1000);
        
                // おばけがクリックされたら実行
                function ghost_click() { // 高速移動
                    // 移動時間の最小最大
                    let min = 0.5;let max = 1.5;
                    let number = Math.floor( Math.random() * ghost_positions.length);
                    document.getElementById('ghost-icon').style.border = '2px solid skyblue';
                    ghost_move(
                        x = window.outerWidth * ghost_positions[number][0],
                        y = window.outerHeight * ghost_positions[number][1],
                        time = (Math.floor( Math.random() * (max + 1 - min) ) + min) * 1000
                    );
                }
        
            </script>
        
        </div>
  
    </body>

</html>


この全体コードは僕がおばけの開発ように作成したものなので、titleは実験場となっています。



JavaScriptを学習する

  • ボタンで自動おすすめ記事
  • 記事一覧サイトへの自動移行
  • ゲームを作る

など

JavaScriptは少し難しいですが、使えると本当にたくさんのことができるようになります。自由度がとっても上がりますよ。



プログラミングに興味のある方はぜひ勉強してみてください。

次の記事ではcanvasを用いて、ゲームマップを作る方法について解説しています。

riaimu.hateblo.jp


おすすめの参考書

注意:JavaScriptはhtml(cssも一応)と組み合わせて使用する言語だと言えます。つまり、それらを勉強する前にjsを習得してもあんまり楽しくないです。
なので、まずはhtml,cssについて勉強してみましょう。

もちろん理解できている方にはjsをお勧めします。

riaimu.hateblo.jp



AmazonからJavaScriptの参考書をご紹介します。



特にふりがなプログラミングは最初から読んでみたかったです。





記事を読んでいただきありがとうございます。
おばけがサイト内で動き回るプログラムは前々から作ってみたいと思っていました。最初はプログラムを作っていましたが、後から見返した時何か面白いことがあるかもしれないので、こうして記事にしています。
なので、「ユーザーの悩みを解決する」よりも僕の記録として楽しんでいただけると嬉しいです。




【おすすめ記事】
riaimu.hateblo.jp
riaimu.hateblo.jp
riaimu.hateblo.jp





記事の内容に間違いがあった場合は、Twitterもしくはお問い合わせフォームからお伝えください。