ラベル HTML5/JavaScript/CSS の投稿を表示しています。 すべての投稿を表示
ラベル HTML5/JavaScript/CSS の投稿を表示しています。 すべての投稿を表示

2020年4月4日土曜日

[Enchant.js]スマホで全画面表示した際、Viewportのサイズに画面を合わせる方法


Enchant.jsで沼にはまったところがあったので、ここにメモしておきます。



Enchant.jsをはじめとして、HTML5ベースに作ったWebアプリをスマホとかで全画面表示(ネイティブアプリっぽく表示)するとき、 metaタグでviewportとかの設定をすると思います。





ただ、このときに、ゲーム画面が全体にフィットしないという問題が起こりました。
スマホの解像度に合わせて作ったにも関わらず...




結構これで悩んだのですが、いろいろ試したり調べたりするうちに、「viewportで表示した際、innerHeightを取得すると、最初の一回 目はアドレスバーを考慮した数値が出てくる」ということがわかりました。


viewportでの表示の時と、普通のブラウザの時の表示で何が異なるかというと、主にアドレスバーの有無です。



ブラウザで表示した際のinnerHeightが500だったとします。
viewportで表示すると、アドレスバーがない分これより値が大きくなりそうですが、一回目の表示の際は500となることがわかりました。

で、その状態でリロードすると、innerHeightの値が500より大きい、すなわち、アドレスバーが消えた状態の高さが取得できました。




実際には、最初に読み込んでからしばらく時間が経ってからinnerHeightを取得すれば、正しい高さが取得できるようでしたので、次のような コードを実装すればこの問題は解決できると思われます。



var game = new Core(540, 930);

var CenterMove = function (core) {
    var scale = Math.min(window.innerWidth / core.width, window.innerHeight / core.height);
    core.scale = scale;

    var stage = document.getElementById('enchant-stage');
    stage.style.position = 'absolute';
    stage.style.top = (window.innerHeight - (core.height * core.scale)) / 2 + 'px';
    stage.style.left = (window.innerWidth - (core.width * core.scale)) / 2 + 'px';
    core._pageX = (window.innerWidth - (core.width * core.scale)) / 2;
    core._pageY = (window.innerHeight - (core.height * core.scale)) / 2;
};

setTimeout(function () {
    CenterMove(game);
}, 55);
  


最初にgameオブジェクトを作成します。


正しい高さを取得するため55ms後にCenterMove関数を呼び出します。



ここでは、縦、横においてそれぞれ、拡大縮小率を計算し値が小さいものを選んで、それをgameオブジェクトのスケールとします。



その後は、画面を中央に表示しています。




以上です。


2020年1月10日金曜日

[Enchant.js]Granim.jsを利用してマ〇オの無敵状態(スターの状態)みたいな虹色グラデーションをつくる


はい、こんにちは。
@CortanMathです。


少し遅れましたが、あけましておめでとうございます。



僕は今年(去年?)年賀状代わりにこんなものを作りました。
Enchant.jsを使用した横スクロールゲームになっています。





一応こちらにリンク張っておきます。

http://kumakuma.life.coocan.jp/ShiraoAkeome/

上のはバージョン1になります。



で、こっちがバージョン2。

http://kumakuma.life.coocan.jp/ShiraoAkeome2/

何が違うがおわかりいただけたでしょうか。




答えは無敵状態機能です。

以前のバージョンでは、1倍速⇒2倍速⇒8倍速という感じにスクロールスピードを上げ、難易度を高めていたのですが、さすがに8倍速は無理がありまし た。

ということで、いのししを5回倒すと永遠に無敵状態に入るようなアップデートを行いました。
(ゲームとしての仕様に問題があることに関してはアップデートされませんでした。)


実際に完成したものがこんな感じになっています。
無敵状態は動画の後半の方です。



でこれをつくるにあたって課題となったのが、マ〇オの無敵状態みたいにキラキラ光らせる処理で す。



無敵状態なったとき敵に当たったらその敵を倒すという処理は、フラグの変数を使用すれば割と簡単に実現できます。


ただ、この無敵状態を表すため、プレイヤーを虹色にするという処理に悩みました。




いろいろ調べた結果、Granim.jsというライブラリを使用することに決めました。



Granim.jsについて



今回使用するGranim.jsというのは、いろいろな色のグラデーションをアニメーションさ せることのできるJavaScriptライブラリです。



公式サイトにデモやソースコードが載っているので、そちらを見た方が理解がはやいと思います。



公式サイト:
https://sarcadass.github.io/granim.js/index.html


デモページ:
https://sarcadass.github.io/granim.js/examples.html



見てるだけでも、結構面白いです。



使い方は簡単で、以下のページからGranim.js本体をダウンロードし、HTMLファイルから読み込むだけです。

https://github.com/sarcadass/granim.js/releases


解凍したうちの「dist」フォルダ内に「granim.js」「granim.min.js」があると思います。


どちらか一方(どちらでもよい。minは圧縮されたファイルになっていて少し容量が少ない。)をコピーして使う感じですね。


公式サイトにサンプルコードがいろいろ載っているので、迷うことはあまりないと思います。


現在(この記事執筆時)の最新バージョンはv2.0.0です。


実際に無敵状態にする



では実際にやってみましょう。


まずは、Enchant.jsとGranim.jsをHTMLから読み込みます。


<script type="text/javascript" src="./js/lib/enchant.min.js"></script>
<script type="text/javascript" src="./js/lib/granim.min.js"></script>


今回はどちらも、minとなっている方(軽量化タイプ)を選びました。

ファイルのパスは各自のフォルダ構造に合わせて設定してください。



次にJavaScriptの方を書いていきます。

Enchant.jsを使用しますので、詳しい全体のコードの書き方などは、Enchant.jsのサイトを参考にしてください。

今回は無敵処理部分のみ載せておきます。



ちょっと長いです。


    //無敵状態画像
    var player_surface = new Surface(360, 180);

    var tmpCanvas = document.createElement('canvas');
    tmpCanvas.width = 360;
    tmpCanvas.height = 180;

    document.body.appendChild(tmpCanvas);
    tmpCanvas.style.visibility = 'hidden';

    var granimInstance = new Granim({
        element: tmpCanvas,
        direction: 'left-right',
        opacity: [1, 1],
        states: {
            "default-state": {
                gradients: [
                    ['#EB3349', '#F45C43'],
                    ['#FF8008', '#FFC837'],
                    ['#4CB8C4', '#3CD3AD'],
                    ['#24C6DC', '#514A9D'],
                    ['#FF512F', '#DD2476'],
                    ['#DA22FF', '#9733EE']
                ],
                transitionSpeed: 100
            }
        }
    });

    var mask = new Image();
    mask.src = './img/player.png';

    var invincibleDraw = function () {
        player_surface.context.globalCompositeOperation = 'source-over';
        player_surface.context.drawImage(tmpCanvas, 0, 0);
        player_surface.context.globalCompositeOperation = 'destination-in';
        player_surface.context.drawImage(mask, 0, 0);
    };

    mask.onload = function () {
        invincibleDraw();
    };


    var player = new Sprite(120, 180);
    player.image = game.assets['./img/player.png'];
    player.y = 120;
    main.addChild(player);


まず前提として、プレイヤーの画像はこんな感じになっていて、アニメーション用に3パターンが あります。
これらは、360×180の1つの画像にまとまっていまして、一つのパターンあたり120×180のサイズになっています。



上に乗せたコードは、メインシーン読み込み時に一度だけ実行されるものです。

player_surfaceというのは、無敵状態の画像を入れておく Surface(Canvasをラップしたもの)です。



大まかな流れとしては、

player_surfaceに無敵用のキラキラ画像を生成しておく。

通常時はplayerのスプライトの画像として、直接元の画像ファイルを指定しておく。

無敵状態に入った場合、playerのスプライトの画像を、player_surfaceに切り替える。


という感じですね。


スプライト自体は、120×180にし、サーフェイスの方はアニメーションの切り替えのため360×180にしてあります。

また、フレームの切り替えは、ここには載せていませんが、onenterframeの部分に書いてあります。




で、ここからが今回の本題となります。

どうやって、虹色画像を生成するかです。



Granim.jsではキャンバスを指定し、そこに虹色のグラデーションを生成することができます。

ということで今回は、360×180の一時的なキャンバスを生成し、bodyに追加後(追加しないと正しく動作しませんでした。)、非表示にしていま す。

あくまで、プレイヤーの全体の画像と同じサイズの虹色グラデーションを作り、画面上では見えないようにしているだけです。

ちなみに、このグラデーションは勝手にアニメーションされます。



granimInstanceというのが、実際にグラデーションを生成している部分ですね。



elementに描画するキャンバスを指定しています。


本来はCSSセレクタで指定するようです。
なので、一時的なキャンバスにidを設定し、element部分に「#id名」と指定してみましたが、うまく動きませんでした。


なぜか、キャンバスの変数を指定したら動きました。



Granim.jsの詳しい使い方は、公式サイトのAPIのところを見てください。



次に進みます。



先ほど作った虹色グラデーション画像を元のプレイヤー画像でマスクし、player_surfaceに描画します。



要するに、虹色グラデーション画像のうち、キャラ画像の形の部分だけが残る画像が生成されるわけです。



ただ、これだけだと、グラデーションがアニメーションされないので、invincibleDraw関数は毎フレーム事に呼び出します。



後は、無敵状態を表すフラグを変数で用意しておき、無敵の条件が揃ったらそれをtrueにする。

それをもとに、スプライトの画像をplayer_surfaceに切り替えたり、敵を倒す処理を書いたりすればよいわけです。





Granim.jsは別に無敵処理とかじゃなくても面白く使えると思うので、ぜひ試してみてください。




ではさようなら。
またお会いしましょう。

もしよければ、LINEスタンプの方もよろしくお願いします。


https://line.me/S/sticker/7072968

無言で何かを伝えよう とする白男くん - LINE スタンプ | LINE STORE

白男くんが何もしゃべらずに、何か大事なことを伝えようとするスタンプです。


お そらくいつでも使えるLINEスタンプ集


2019年12月25日水曜日

Enchant.js製のスマホ向けアプリを横向き(縦)回転の表示に強制させる方法!また、画面を中央に表示する


はい、こんにちは。
@CortanMathです。


今回は、JavaScript製の軽量ゲームエンジン「Enchant.js」についての記事になります。

別にEnchant.jsを使用しない場合においても応用できると思います。


で、内容は、「JavaScriptでスマホ向けアプリを作成した際、画面の向きを固定する」「画面を中央に表示する」といったものになります。





Enchant.js製のスマホ向けゲームで、画面横向き(または縦向き)のみでプレイしてほしい場合などがあると思います。
ただ、標準のままだとスマホの傾きにより画面が回転し、ゲーム画面サイズが小さくなってしまったりしますよね。


ということで、「画面の向きを判断し、もし目的の向きでなかった場合、その向きに端末を回転させるよう促す」ということを今回やりたいと思います。




最初のころは、何があっても強制的に画面を横向き(縦向き)に表示したいと考えていました。

「画面の向きを判定」 ⇒ 「もし目的の向きでなかった場合、ゲーム画面を無理やり90度回転させ、調節する」

という予定でしたが、スプライトとかの回転はできても画面自体の回転は良くわからなかったのと、画面横向きでプレイさせたい際に、縦にもった状態で横 向き表示する必要はないので、ただ警告を出すだけにしました。






また、それに加えて、Enchant.jsのゲーム画面を中央に表示するようにしたいと思います。
これをしないとゲーム画面は常に左上につめられて表示されるため、縦横比の異なるスマホとかでは、左右の余白のバランスがおかしなことになってしまい ます。

スマホによって余白ができてしまうのはどうしようもないので、せめて、全体のバランスが良くなるようにしたいと思います。



では、やっていきましょう。


Enchant.jsでゲーム画面を横向き(縦向き)に



これを行うためにも、まず、スマホの向きが「今、縦なのか横なのか」を判断する必要がありま す。


書き方はいろいろあるのですが、今回はその中でもシンプルな書き方をします。


if (window.innerHeight > window.innerWidth) {
            //縦向きだった場合
        }


こんな感じです。

画面の幅と高さを出して、その長さの比較をするという簡単なやり方です。


で、この縦向きだった場合に、「端末を横向きにしてください。」みたいなメッセージを出せばよいわけです。


実際にメッセージ表示を実装するとこうなります。
gameオブジェクトがロードされたところに書きます。



var alert = new Scene();
alert.backgroundColor = 'black';

var message = new Label('端末を<br><br>横向きにして<br><br>持ってください。');
message.color = 'white';
message.font = '50px "Arial"';
message.textAlign = 'center';
message.x = (game.width - message._boundWidth) / 2;

alert.addChild(message);

if (window.innerHeight > window.innerWidth) {
    game.replaceScene(alert);
}

こんな感じです。


これで最初ゲームがロードされた際、もしスマホが縦向きだったら、横向きにしてくれという警告を出しています。
また、最初から横向きだった場合は、標準のrootSceneが読み込まれます。


rootSceneにゲーム本体を用意すればよいわけです。


それが嫌な人は、上記のコードのif分にelseを足し、そこにゲームのメインシーン(もしくはOPシーン)を読み込むように書けばいいと思います。




ただこのコードのままだと問題があります。


ロード時に端末が横向きだった場合は良いですが、縦向きだった場合、メッセージだけ表示してそれ以降なにも進みません。


端末を傾けて横にしたら~、という処理が必要なわけです。

その部分はこう書きます。
window.onload = function(){}; の外にでも書いてください。


  
  window.addEventListener("orientationchange", function () {
    location.reload();
});
  

こんな感じです。

端末を90度傾けたら、みたいなイベントのようです。(正確には違うかもしれない)
だから、横から縦にしても、縦から横にしても発生します。


で、そのイベント発生後、ウェブサイト自体をリロードしています。

そうすると先ほどのコードがまた読み込まれ、今度は、ゲームがスタートするという仕組みです。



リロードするというのはあまり賢い書き方ではないと思いますが、コードを簡潔にし、減らすために採用しました。


これで、画面の向きが解決しました。


画面を中央に表示する



今度はゲームの画面を中央に表示します。


  
  var game = new Core(640, 360);

var CenterMove = function (core) {
    var stage = document.getElementById('enchant-stage');
    stage.style.position = 'absolute';
    stage.style.top = (window.innerHeight - (core.height * core.scale)) / 2 + 'px';
    stage.style.left = (window.innerWidth - (core.width * core.scale)) / 2 + 'px';
    core._pageX = (window.innerWidth - (core.width * core.scale)) / 2;
    core._pageY = (window.innerHeight - (core.height * core.scale)) / 2;
};

CenterMove(game);

こんな感じです。


gameオブジェクトを渡し、それをちょうど画面真ん中に来るよう移動しています。



以上です。



ではさようなら。
またお会いしましょう。

もしよければ、LINEスタンプの方もよろしくお願いします。


https://line.me/S/sticker/7072968

無言で何かを伝えよう とする白男くん - LINE スタンプ | LINE STORE

白男くんが何もしゃべらずに、何か大事なことを伝えようとするスタンプです。


お そらくいつでも使えるLINEスタンプ集


2019年8月25日日曜日

[Enchant.js]テトリスもどきをつくってみたかった...~落ちて2つに破壊する~


こんにちは。

皆さん、「Enchant.js」ってご存知ですか?


「Enchant.js」とは


Enchant.jsとは、ゲームやアプリを簡単に作れるJavaScriptのライブラリで す。
HTML5+JavaScript+CSSのWebアプリを作る際に使えます。


オープンソース(MITライセン ス)なので、無料で使え、また、商用利用などもできます。


Webアプリなので、サーバーにアップロードしておけば、PCだけでなくiPhone・Androidなどのスマホでもアプリを実行できます。

Webアプリの状態でも全画面表示にしてアプリっぽくしたり、あるいはPWAにしてオフライン対応にすることもできます。
もしくは、Apache Cordovaでネイティブアプリにもできます。


要するにマルチプラットフォームなわけです。



こちらの公式サイトからダウンロードできます。

http://enchantjs.com/ja/


Enchant.js本体だけでなく、ゲームに使える画像素材やプラグインなども入っています。


テ■リスをつくりたかったのに...


今回は、このEnchant.jsを使って、テ■リスもどきをつくっていこうと思います。

あくまでも流れを紹介するだけなので、詳しいことは省きます。
チュートリアルではないので、ご了承ください。


ファイルなどの準備

まずは、ファイルなどの準備をしていきます。



最初に用意するのは、HTMLファイルと、JavaScriptファイルですね。


フォルダ構成はこんな感じです。



次に画像の作成を行いました。

ゲーム自体に使う画像と、アイコンにする画像の2つを用意しました。
フリーの画像編集ソフト「GIMP」を用いて作成しております。



ゲームに使う方の画像は、色違いでひとマスずつ作成し、プログラムの方で各プロックを作成する ようにしました。

また、アイコンの方は、スマホ(Android or iPhone)でホーム画面に追加したときに表示されるアプリアイコン画像です。


続きまして、HTMLの中身を用意します。

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="utf-8" />
    <title>T□TRIS</title>

    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <link rel="apple-touch-icon" href="./img/icon.png">

    <script type="text/javascript" src="./js/lib/enchant.min.js"></script>
    <script type="text/javascript" src="./js/main.js"></script>

    <style>
        body {
            padding: 0;
            margin: 0;
            background: #000000;
        }
    </style>
</head>

<body>

</body>

</html>


こんな感じです。

メインのJavaScriptファイル及びEnchant.jsの読み込み、アイコンの設定、 スマホでホーム画面に追加したときの全画面設定などを行っております。


メインのJavaScriptを書いていく

ゲームを動作させるメインのプログラムをJavaScriptで書いていきます。

実装させる基本的な処理はこちらです。

  • スタート画面作成
  • 枠組みの表示
  • 各ブロック生成
  • テ■リス落下
  • 移動、回転処理
  • 着地処理
  • 一列揃ったら消す
  • スコアを付ける

では、早速書いていきましょう。


スタート画面の作成は、特に難しいことはないですね。

シーンを2つ作って、一つをスタート画面、もう一方をゲームメイン画面とします。
最初はスタートのシーンを表示して、それをタッチするとメイン画面に切り替えるようにしてあります。



枠組みは、Enchant.jsのマップ機能を使用して作成しました。
最初に用意した画像のうち、茶色のマスを使用しています。



次に、ブロックの生成ですね。


最初に、このような画像を用意したと思います。

一番左のマスは、先ほど枠組みとして使用しましたから、テ■リスのブロック生成には、残りの4 色を用います。


作るブロックの形は、以下の5種類です。
色は気にしないでください。
形ごとに4色分のブロックを作りますので、合計20種のブロックができます。(本家だと形ごとに色が決まってたりしましたっけ?そのへんあやふやです みません。)



形の種類の数が本家と違うと思いますが、著作権の問題のためです。
そのまま同じにはできないわけです。


ではこの部分のソースコードです。

var tetris_map = [
                [
                    [1, 1, 1, 1]
                ],
                [
                    [1, 1],
                    [1, 1]
                ],
                [
                    [0, 1, 0],
                    [1, 1, 1]
                ],
                [
                    [1, 0, 0],
                    [1, 1, 1]
                ],
                [
                    [1, 1, 0],
                    [0, 1, 1]
                ]
            ];

            var tetris_surface = new Array();


            for (var i = 0; i <= 3; i++) {
                for (var j = 0; j <= 4; j++) {

                    var tmp_surface = new Surface(tetris_map[j][0].length * 32, tetris_map[j].length * 32);

                    for (var k = 0; k < tetris_map[j].length; k++) {
                        for (var l = 0; l < tetris_map[j][k].length; l++) {

                            if (tetris_map[j][k][l] == 1) {
                                tmp_surface.draw(game.assets['./img/tetris.png'], (i + 1) * 32, 0, 32, 32, l * 32, k * 32, 32, 32);
                            }

                        }
                    }

                    tetris_surface.push(tmp_surface);

                }
            }


Surfaceというのは、Canvasをラップしたもので、Enchant.js上で Canvasを使用するのに使います
Spriteのimageに代入することで、画像として表示できます。


ここでは、あらかじめ配列にブロックの形を用意しておいて、4重ループで、それに応じて20のブロックを生成し、tetris_surfaceという 配列にプッシュしています。


次に行きます。

テトリスの落下は、特に難しいことはないですね。
ただy座標を足していくだけです。

個人的にはなめらかに動いてほしくないので、毎フレームちょっとずつ動かすのではなく、何フ レームかごとに1マス分足しています。

この何フレームかごとというのは、if分と変数を使って処理しています。


では次にいきましょう。


次は移動回転処理ですね。

これが地味に難しかったです。


まず操作方法ですが、PCとスマホのどちらでも動くようにしたいので、キー操作とタッチ操作の両方を実装します。


<PCの場合>
・方向キーの左右で移動
・方向キーの上で右回転、「/」キーで左回転
・方向キーの下で加速落下


<スマホの場合>

・画面のタップで移動
(左半分をタップすれば左、右半分なら右)


・左から右へドラッグで、右回転
右から左へドラッグで、左回転


・上から下へドラッグで、加速落下



操作の部分はそこまで難しくないです。
が、問題は実際の回転の部分なのです。


正方形のブロックは回転の中心がちょうどいいところにくるので大丈夫なのですが、そのほかのブロックは回転の中心が微妙なところにくるので、回転した 瞬間、枠組みの配列に対して、ズレが生じるのです。


ということで、回転後に計算した分のずれを、x軸、y軸に足して、調節することにしました。


tetrisという変数は、メインの動かしているスプライトです。

tetris_wとtetris_hは、それぞれ現在のブロックの幅と高さが入る変数です。
単純にtetris.widthとtetris.heightでは、回転した後も値が変わらず、現在表示されている状態での幅や高さにならないので、 わざわざこのような変数を用意して、回転に応じて値を調節しています。

tetris_center_xは、スプライトの中心のx座標です。
回転処理をしているうちにだんだん右に移動していったり、左にいったりなどということが起こってしまったので、この変数を使用して、それを防止してい ます。

これらの変数はあらかじめ宣言し、落下するブロックを生成した段階で、値を代入しています。


ソースコードを見てもらった方が早いですね。


var change_wh = function () {
    var tmp = tetris_w;
    tetris_w = tetris_h;
    tetris_h = tmp;
};

var tetris_left_rote = function () {
    tetris.rotation -= 90;
    change_wh();
    tetris.x = Math.floor((tetris.x + (tetris.width - tetris_w) / 2 - 4) / 32) * 32 + 4 - (tetris.width - tetris_w) / 2;
    tetris.y = Math.round((tetris.y + (tetris.height - tetris_h) / 2 - 80) / 32) * 32 + 80 - (tetris.height - tetris_h) / 2;

    if (tetris_center_x - 32 >= tetris.x + tetris.width / 2) {
        tetris.x += 32;
        tetris_center_x = tetris.x + tetris.width / 2
    }

    if (tetris.x + ((tetris.width - tetris_w) / 2) < 36) {
        tetris.x = 36 - (tetris.width - tetris_w) / 2;
    }
    if (tetris.x + tetris.width - ((tetris.width - tetris_w) / 2) > 324) {
        tetris.x = 324 - (tetris.width - ((tetris.width - tetris_w) / 2));
    }
};
var tetris_right_rote = function () {
    tetris.rotation += 90;
    change_wh();
    tetris.x = Math.ceil((tetris.x + (tetris.width - tetris_w) / 2 - 4) / 32) * 32 + 4 - (tetris.width - tetris_w) / 2;
    tetris.y = Math.round((tetris.y + (tetris.height - tetris_h) / 2 - 80) / 32) * 32 + 80 - (tetris.height - tetris_h) / 2;

    if (tetris_center_x + 32 <= tetris.x + tetris.width / 2) {
        tetris.x -= 32;
        tetris_center_x = tetris.x + tetris.width / 2
    }

    if (tetris.x + ((tetris.width - tetris_w) / 2) < 36) {
        tetris.x = 36 - (tetris.width - tetris_w) / 2;
    }
    if (tetris.x + tetris.width - ((tetris.width - tetris_w) / 2) > 324) {
        tetris.x = 324 - (tetris.width - ((tetris.width - tetris_w) / 2));
    }
};


回転した後に左右の枠をはみ出た際の処理も書いています。

移動でも同様な感じでズレの判定をしています。



では、次に行きましょう。

次は着地処理及び、消去処理ですが、ここで問題発生です。

ここら辺の書き方がよくわからなかった。
僕には少し早かったみたいです。


ということで、この部分の処理をやめて、違う処理を入れました。

落下し、着地した瞬間、こんな感じにします。



地面についた瞬間、2つに破壊されるんですね。



しょうもな...


ということで、やっていきましょう。


テ■リスを2つに破壊する



では、どうやって先ほどのように割るのかというと、JavaScriptでCanvasのピク セル操作を行うことで、破壊されたように見せます。


CanvasのコンテキストのgetImageDataで指定した範囲のピクセルデータを取得し、putImageDataで逆に書き出します。


実際に、プログラムを見てもらいましょう。

var mod = function (i, j) {
    return (i % j) < 0 ? (i % j) + 0 + (j < 0 ? -j : j) : (i % j + 0);
};

var destroy = function (s) {

    var speed = s;

    tetris.visible = false;

    var destroy_imagedata = tetris.image.context.getImageData(0, 0, tetris.width, tetris.height);

    var destroy_surface_left = new Surface(tetris.width, tetris.height);
    var destroy_surface_right = new Surface(tetris.width, tetris.height);

    var destroy_left = new Sprite(tetris.width, tetris.height);
    var destroy_right = new Sprite(tetris.width, tetris.height);


    main.addChild(destroy_left);
    main.addChild(destroy_right);



    destroy_left.x = tetris.x;
    destroy_left.y = tetris.y;
    destroy_left.rotation = tetris.rotation;
    destroy_right.x = tetris.x;
    destroy_right.y = tetris.y;
    destroy_right.rotation = tetris.rotation;


    switch (mod(tetris.rotation / 90, 4)) {
        case 0:
            for (var h = 0; h < tetris.height; h++) {
                var r = Math.floor(Math.random() * 20) - 9;

                for (var w = 0; w < tetris.width; w++) {

                    if (w == (tetris.width / 2) + r - 1) {
                        destroy_surface_left.context.putImageData(destroy_imagedata, 0, 0, 0, h, w + 1, 1);
                    }
                    if (w == tetris.width - 1) {
                        destroy_surface_right.context.putImageData(destroy_imagedata, 0, 0, (tetris.width / 2) + r, h, tetris.width - ((tetris.width / 2) + r), 1);
                    }
                }
            }
            break;
        case 1:
            for (var w = 0; w < tetris.width; w++) {
                var r = Math.floor(Math.random() * 20) - 9;

                for (var h = 0; h < tetris.height; h++) {

                    if (h == (tetris.height / 2) + r - 1) {
                        destroy_surface_right.context.putImageData(destroy_imagedata, 0, 0, w, 0, 1, h + 1);
                    }
                    if (h == tetris.height - 1) {
                        destroy_surface_left.context.putImageData(destroy_imagedata, 0, 0, w, (tetris.height / 2) + r, 1, tetris.height - ((tetris.height / 2) + r));
                    }

                }

            }
            break;
        case 2:
            for (var h = 0; h < tetris.height; h++) {
                var r = Math.floor(Math.random() * 20) - 9;

                for (var w = 0; w < tetris.width; w++) {

                    if (w == (tetris.width / 2) + r - 1) {
                        destroy_surface_right.context.putImageData(destroy_imagedata, 0, 0, 0, h, w + 1, 1);
                    }
                    if (w == tetris.width - 1) {
                        destroy_surface_left.context.putImageData(destroy_imagedata, 0, 0, (tetris.width / 2) + r, h, tetris.width - ((tetris.width / 2) + r), 1);
                    }
                }
            }
            break;
        case 3:
            for (var w = 0; w < tetris.width; w++) {
                var r = Math.floor(Math.random() * 20) - 9;

                for (var h = 0; h < tetris.height; h++) {

                    if (h == (tetris.height / 2) + r - 1) {
                        destroy_surface_left.context.putImageData(destroy_imagedata, 0, 0, w, 0, 1, h + 1);
                    }
                    if (h == tetris.height - 1) {
                        destroy_surface_right.context.putImageData(destroy_imagedata, 0, 0, w, (tetris.height / 2) + r, 1, tetris.height - ((tetris.height / 2) + r));
                    }

                }

            }
            break;

    }



    destroy_left.image = destroy_surface_left;
    destroy_right.image = destroy_surface_right;


    destroy_left.tl.rotateBy(-270, speed);
    destroy_right.tl.rotateBy(270, speed)
    destroy_left.tl.and();
    destroy_right.tl.and();
    destroy_left.tl.moveBy(-200, 300, speed);
    destroy_right.tl.moveBy(200, 300, speed);

    destroy_right.tl.then(function () {
        main.removeChild(destroy_left);
        main.removeChild(destroy_right);

        createTetris();
        frame_cnt = 0;
        frame_max = 30;
        touch_down = false;

        down_fg = true;
    });

};


まず、破壊された後、左側に落ちるスプライトと、右側に落ちるスプライトの2つを用意します。


ブロックの回転具合によって、分割する箇所が異なるので、そこのところを場合分けで書いていきます。



いよいよピクセル処理の出番です。


Enchant.jsのSurfaceの方にも、getPixelとsetPixelというピクセル処理の機能が備わっているのですが、こちらは、1 ピクセルごとに処理するため、動作がかなり重いです。


なので、今回はラップされていないもともとの「getImageData」と「putImageData」を使用します。



回転による場合分けをした後、2重ループを使い、ブロックの幅をランダムな長さにしながら、左右のスプライトに分けています。



分けた後は、もとのメインスプライトを非表示にし、2つのスプライトをうまく動かして、落下させます。

落下スピードに応じて、破壊後の動きにも変化をつけています。


破壊処理は以上ですね。


スコアは適当につけています。


かなり、はしょったわかりにくい説明だったと思います。
ソースコードもこれだけではよくわからないでしょう。


ということで、ソースコードの全体は、こちらから見れます。

http://kumakuma.life.coocan.jp/DestroyTetris/js/main.js


完成



プレイ動画(?)みたいなのは、この動画の後半にあります。


また、こちらのURLから遊ぶ(?)ことができます。

http://kumakuma.life.coocan.jp/DestroyTetris/


スマホで開いて、ホーム画面に追加するとアプリっぽくなります。
オフラインには対応してませんが。


では、これで終わります。
Enchant.js面白いので、ぜひ触ってみてください。


2019年4月21日日曜日

ESP32-DevKitCとArduino IDEでIoT開発!~Wifiを経由し、スマホやパソコンからGPIOの操作をする~


こんにちは。
少々お久しぶりです。


皆さん、「IoT」っていう言葉、聞いたことありますか?


おそらく一度はあると思います。


IoTとは「Internet of Things」の略で、モノのインターネットとも呼ばれています。


様々な「モノ」がインターネットに接続され、情報交換することにより互いを制御する仕組みのことを指します。



スマホから家電を操作する、みたいなものがよくあるパターンですね。


現在、このIoTという技術がかなり注目され、実用化に向け発展し続けています。



ということで今回は、ESP32-DevKitC wroom-32開発ボードというものとArduino開発環境を用い、自宅で簡単にIoTを実現してみようと思います。


ESP32-DevKitC wroom-32開発ボードとは



では、そもそも、「ESP32-DevKitC」ってなんでしょうか。



Espressif Systems 社の製品に、「ESP-WROOM-32」というものがあります。

これは、Wi-FiとBluetoothの機能が付いているモジュールです。



これだけで、Wi-FiとBluetoothという無線通信機能を電子工作に取り入れられるのでとても便利です。

しかも格安。


専用のソフトウェアからプログラムを書き込むことで動作しますが、それだけでなく、プラグインを入れることでArduino IDEで開発することもできます。




ただ、半田付けや基板実装などを自分でする必要があるので、特に初心者にとっては抵抗があります。




そんな時に便利なのが、この「ESP32-DevKitC wroom-32開発ボード」です。

モジュール「ESP-WROOM-32」に、「USBシリアル変換機能」や「5V ⇒ 3.3V変換」、「ピン」などが搭載されており、非常に手軽にWi-Fi・Bluetoothを用いた電子工作が楽しめます。


価格は、約1,480円です。


秋月電子で購入できます。

[秋月電子]ESP32-DevKitC ESP-WROOM-32開発ボード


では実際に使用してみましょう。


Arduino IDEにプラグインの導入(開発環境)



Arduino IDEでESP32-DevKitC wroom-32開発ボードにプログラムを書き込んだりできるようになるためには、プラグインを入れる必要があります。


以前は、GitHubからファイルをダウンロードして、コマンドをたたいて...
みたいな面倒な作業が必要だったようですが、現在、その必要はなく、超簡単にプラグインの導入ができます。


1.Arduino IDEの最新版をダウンロード・インストール

Arduino IDEの最新版をこちらからダウンロード・インストールしてください。
インストール版でもポータブル版でも、どちらでもよいです。

https://www.arduino.cc/en/Main/Software


すでにパソコンに入っているという方はそれでも構いませんが、もし、そのIDEのバージョンが低い場合、今回の操作によるプラグイン導入ができないか もしれません。

ですので、素直に最新版を使用することをお勧めします。


2.ボードマネージャ追加

では、Arduino IDEにESP32用のプラグインを追加していきます。


まず、「ファイル」⇒「環境設定」を開いてください。



そうしましたら、「追加のボードマネージャのURL」というところに、以下のURLを追加してください。

https://dl.espressif.com/dl/package_esp32_index.json


できましたら、「OK」で閉じます。


次に、「ツール」⇒「ボード」⇒「ボードマネージャ...」をクリックします。




「ESP32」と検索して出てきたものを、インストールします。



これでプラグインのインストールは完了です。


ではここで、ボードに、「ESP32 Dev Module」を選択しましょう。



3.USBドライバのインストール

Arduino IDEでESP32の開発環境ができましたが、このままでは、PCがESP32-DevKitC wroom-32開発ボードを認識してくれない可能性があります。


きちんと認識されていないと、開発ボードとパソコンをUSBでつないでも、「シリアルポート」で選択できません。


そんな場合は、こちらのドライバをインストールしてみてください。

https://jp.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers

自分のOSにあったものを使用してください。


これで開発環境は一通りそろいました。


簡単なWi-Fiプログラム(サンプルの改変)を動かしてみる



準備もできましたし、実際に、Wi-Fi経由でブラウザからLEDを操作するプログラムを動か してみましょう。

サンプルプログラムをやや改変しています。


もとにしたサンプルプログラムは、「ファイル」>「スケッチ例」>「WiFi」> 「SimpleWiFiServer」です。

ssid及びpasswordは、各自、自宅のWiFiルーターのものに書き換えてください。

#include <WiFi.h>

const char* ssid     = "自宅のWiFiルーターのSSID";
const char* password = "自宅のWiFiルーターのパスワード";

const char html[] = "<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'>\
<meta name='viewport' content='width=device-width,initial-scale=1'>\
<style>input{margin:8px;width:240px;height:80px;}</style>\
<title>LEDWiFiSwitch</title></head>\
<body><p style='font-size:16px;'>LEDWiFiSwitch</p>\
<form method='get'>\
<input type='submit' name='on' value='ON'/><br>\
<input type='submit' name='off' value='OFF'/>\
</form></body></html>";

WiFiServer server(80);


void setup()
{
    Serial.begin(115200);
    pinMode(5, OUTPUT);      // set the LED pin mode

    delay(10);

    // We start by connecting to a WiFi network

    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);

    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println("");
    Serial.println("WiFi connected.");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    
    server.begin();

}

int value = 0;

void loop(){
 WiFiClient client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println("New Client.");           // print a message out the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        if (c == '\n') {                    // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            // the content of the HTTP response follows the header:
            client.print(html);

            // The HTTP response ends with another blank line:
            client.println();
            // break out of the while loop:
            break;
          } else {    // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }

        // Check to see if the client request was "GET /H" or "GET /L":
        if (currentLine.endsWith("GET /?on")) {
     digitalWrite(5, HIGH);               // GET /H turns the LED on
        }
        if (currentLine.endsWith("GET /?off")) {
     digitalWrite(5, LOW);                // GET /L turns the LED off
        }
      }
    }
    // close the connection:
    client.stop();
    Serial.println("Client Disconnected.");
  }
}

こんな感じです。


そうしましたら5番ピンにLEDのプラス側、300Ωくらいの抵抗をかましてGNDに接続します。


接続出来たら、プログラムを書き込んでみましょう。



書き込みが終了すると、シリアルモニタにESP32のipアドレスが表示されますので、同じWiFi下のデバイスで、そのアドレスにアクセスしてみて 下さい。

ボタンが2つ表示されていると思います。


このボタンで、LEDのON/OFFが制御できるわけです。


簡単でしょでしょ。


プログラムや回路を工夫すれば、様々なことに応用できます。



みなさんも、家具のIoT化などに挑戦してみてはいかがでしょうか。



以上です。
では、さようなら。
またお会いしましょう。


2019年3月31日日曜日

ブログ・ホームページでアプリ紹介するなら「アプリーチ」!!その使い方も(例:Blogger)!

皆さん、こんにちは。


ブログやホームページなどのサイトで、スマホアプリを紹介したいことってありますよね。

ただ、普通にアプリストアのリンクを貼ろうとすると、iPhone(iOS)用のApp Store、Android用のGoogle Playストアの2つのリンクを貼らなければなりません。

正直、面倒くさいですよね。


iPhoneのアプリだけ、もしくは、Androidのアプリだけを紹介したいというのであれば、別にこれでもいいのですが、ほとんどの場合は、2つ 同時に表示したいはずです。


そんなときに便利なのが、今回ご紹介する「アプリーチ」というものです。


これを使用することで、紹介したいアプリのiPhone版、Android版へのリンクを画像付きで、作成することができます。



アプリを紹介しているサイトのほとんどが、このアプリーチというものを使用しています。


また、これとアプリ用のASPを組み合わせることで、アプリがインストールされたら報酬が手に入る、なんてこともできますので、とてもおすすめです。


アプリーチはWeb上のサービスなので、ブラウザで簡単に使用できます。


今回は、基本的な使い方を説明します。
とはいっても、超簡単です。


アプリーチの使い方



まずは、アプリーチのサイトにアクセスしましょう。

アプリーチ

では、まずは、CSSの設置からですね。


アプリーチでアプリの紹介HTMLタグを生成すると、「新コード」と「旧コード」の2種類のソースができます。

このどちらかを、サイトのHTML編集のところに貼り付ければいいわけですが、この2つには違いがあります。

旧コードは、紹介タグのデザインCSSも含んでいるので、これを単体で貼り付けるだけで、使用 することができます。
が、その分コードが読みにくくなります。


一方、新コードは、HTMLタグとCSSが分離してあります。
そのため、このコードとは別に、CSSを設定しておく必要がありますが、コードはその分、読みやすいです。


ですので、今回は、新コードを使用していこうと思います。

1.CSSをブログの設定に貼り付ける


こちらのCSSというところをクリックして、でてきたCSSコードをコピーしてください。


そうしましたら、ブログサービスのカスタムCSSなどに貼り付けて、保存してください。

Bloggerならば以下の画像のところになります。



以上で、下準備が終わりました。


2.アプリ検索


紹介したいアプリ名を検索します。


そうしましたら、その下に、iPhoneとAndroid版のアプリが正しく表示されていることを確認してください。


もし、違っていたら、「複数検索」というところをクリックして、アプリを指定し直すことができます。


3.設定


ここでプレビューを見ながら、好みに設定してください。


4.ソースの貼り付け


新コードを、ブログのHTML編集に貼り付けてください。

これで完成です。


実際、このように表示されます。


LINE

LINE

LINE Corporation無料posted withアプリーチ


以上です。
今回は触れませんでしたが、アフィリエイトにつなげることも簡単ですので、興味がある方はぜひ、お試しください。


では、さようなら。
また、次回お会いしましょう。


2019年3月21日木曜日

Bloggerで画像がはみ出る・スマホだと幅8割程度のサイズにしかならないときの解決法

はい、こんにちは。

みなさん、Googleの無料ブログサービス「Blogger」は使っていますか?



無料なのに勝手な広告が入らなくて、SSL化(https)にすることもでき、画像容量が無限...
メリットが多く、素晴らしいブログサービスです。



そんなBloggerですが、画像サイズ(画像の幅など)で困ったことが少々あったので、その問題点、および、解決策をご紹介させていただきます。


僕と同じように困っていた方がいらっしゃいましたら、ぜひ参考にしてください。


では、具体的に何が問題なのかということについて話していきます。


画像の幅が合わない



通常、ブログやホームページに画像を貼り付けるとき、画像の幅を指定して、はみ出ないようにし ますよね。
まあ、Bloggerでも同じはずです。



画像を挿入する際、このような画面が出てきます。
ここで、小・中・大・特大からどれかを選べば、そのサイズの幅にしてくれそうです。


が、しかし。
しかしですよ。


ここでサイズ指定してしまうと、表示される画像の画質が劣化してしまうのです。


困りましたね。

じゃあ、どうするのか。


画像を挿入した後、その画像のimgタグの属性として、「width="640"」みたいに指定してやれば、画質の劣化を抑えつつ、画像サイズを設定 できま す。

(ちなみに、ここで指定した640というのは、bloggerの記事で表示できる画像の最大幅となります。
640px以上の画像ははみ出ます。)



しかし、これでもまだ問題があるのです。


その問題点は主に2つです。

まず、1つは、

いちいちwidthを入力するのが面倒くさい

まあ、しょうもない理由ですが一応書かせてもらいます。

画像が少ししかない場合はいいんですよ。

でも、貼り付ける画像の数が増えれば増えるほど、この作業が大変に感じるようになっていきます。


普段からタグ打ちでブログ書いている人からしたら、あきれるでしょうね。




そして、2つ目は、

スマホ表示だと、画像幅を最大にしても80%くらいしか表示されない

これがなかなか厄介です。
せっかく大きな画像を表示したくても、実際表示すると、小さくなってしまうのですから。


PC版表示ならあまり気にならないんですよ。


しかし、スマホだと、ただでさえ幅が狭いのに、8割が最大幅って...


かと言って、

画 像幅を「width="640"」以上に設定したり、元のサイズの画像を貼ったりすると、PC版表示では、はみ出ます。


スマホ表示では、640px以上の画像幅でも勝手に縮小され、良い感じのサイズに表示してくれるのですが。



こういった問題が、Bloggerの画像表示には潜んでいるのです。
でも、解決方法は簡単です。


今から紹介する設定をするだけで、「PC版で画像がはみ出ること」も「いちいちwidthを設定するのが面倒くさいという問題」も「スマホだと最大幅 に設定し ても8割くらいしか表示されないこと」も全部解決できます。

では、やっていきましょう。


解決策



まず、Bloggerの画面から、「テーマ」⇒「カスタマイズ」を選びます。



そこで、「上級者向け」というところを選び、下の方までスクロールしてください。

「CSSを追加」というのが出てくると思います。


ではそこの一番下の行に、以下のコードを貼り付け、「ブログに適用」を押してください。
これで完了です。


div.separator img {
max-width: 98%;
height:auto; }

どうでしょうか、簡単だったと思います。

このCSSは何をしているかと言いますと、どんな画像を貼っても、画面幅の98%より大きくな らないよ うにしています。
また、高さは自動的に調節されます。


つまり、もう画像幅をいちいち設定しなくても、画面からはみ出るなんてことは起こらなくなったわけです。

そして、画像幅を設定しないわけですから、スマホ表示でも98%までの大きさにしてくれます。
(ただし、それは貼った画像のサイズが640px以上だった場合です。
250pxとかの画像を貼っても、250pxのまま表示されます。
画面いっぱいに表示したい場合は640px以上の画像を貼ってください。)


ただ、あまりにも大きい画像を貼るのはやめましょう。
読み込みに時間がかかってしまいます。


ちなみに、98%という値は、好みの値にしてもかまいません。
100%だとゆとりが消えます。


以上です。
ありがとうございました。

さようなら。
またお会いしましょう。


人気の記事