Web Applicaation」カテゴリーアーカイブ

JavaScript, Moving at a constant speed

前回のコードに跳ね返る処理を入れるにも三角関数は必須だ。
ということで、とりあえずどの方向にも等速にボールを移動させる手段を探す。

Flashゲーム講座&ASサンプル集【三角関数を使った計算について】

‘どの方向にも一定の速度で移動するキャラクター’
というそのものズバリな解説を見つけた、よしコレでいこう。
ActionScript だけど基本計算は JavaScript と同一なはず。

endo blog: Javascript Math.atan2() X軸からポイントまでの角度を取得

って JavaScript には Math.atan2 という便利すぎ関数があるじゃないの。
HTML 中を引っ張った逆方向に移動するのだから

// 角度をラジアンで取得
ラジアン = Math.atan2(Y座標, X座標)
// 移動先の算出
X座標 -= Math.cos(ラジアン) * 移動ピクセル数
Y座標 -= Math.sin(ラジアン) * 移動ピクセル数

あれ、こんな冗談みたいに単純で本当にいいのかな?
試しに前回のを 20/1000 秒毎に 10px 移動する感じに書き換えてみる。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>一定速で移動させる</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    margin: 0;
    overflow: hidden;
    -webkit-text-size-adjust: 100%;
}
#ID_CANVAS {
    background-color: #FFEE00;
}
</style>
<script type="text/javascript"><!--

    var canvas = null;
    var context = null;
    var ball = null;
    var offset = 24;
    // struct
    var ballData = function() {
        this.timer_id = -1;
        this.timer_count = 0;
        this.x = 0;
        this.y = 0;
        //this.move_x = 0;
        //this.move_y = 0;
        this.rotation = 0;
    }
    var BallData = new ballData();

    // Event Handler
    var onBallTouchStart = function(e) {
        e.preventDefault();
    }
    var onBallTouchMove = function(e) {
        if (BallData.timer_id == -1) {
            context.clearRect(0, 0, canvas.width, canvas.height);
            BallData.x = e.touches[0].pageX;
            BallData.y = e.touches[0].pageY;
            context.beginPath();
            context.moveTo(160, 160);
            context.lineTo(BallData.x, BallData.y);
            context.stroke();
            moveBall(BallData.x, BallData.y);
        }
    }
    var onBallTouchEnd = function(e) {
        if (BallData.timer_id == -1) {
            //BallData.move_x = (BallData.x - 160) / 5;
            //BallData.move_y = (BallData.y - 160) / 5;
            BallData.rotation = Math.atan2(BallData.y - 160, BallData.x - 160)
            BallData.timer_count = 20;
            BallData.timer_id = setInterval(onTimer, 20);
        }
    }
    // Timer Handler
    var onTimer = function() {
        if (BallData.timer_count > 0) {
            //BallData.x -= BallData.move_x;
            //BallData.y -= BallData.move_y;
            BallData.x -= Math.cos(BallData.rotation) * 10;
            BallData.y -= Math.sin(BallData.rotation) * 10;
            moveBall(BallData.x, BallData.y);
            BallData.timer_count--;
        } else {
            clearInterval(BallData.timer_id);
            BallData.timer_id = -1;
            context.clearRect(0, 0, canvas.width, canvas.height);
            moveBall(160, 160);
        }
    }
    // Func
    var moveBall = function(x, y) {
        ball.style.left = x - offset + "px";
        ball.style.top  = y - offset + "px";
    }
    // Connect
    var init = function() {
        if (window.TouchEvent) {
            canvas = document.getElementById("ID_CANVAS");
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;// - 100;
            context = canvas.getContext("2d");
            ball = document.getElementById("ID_BALL");
            ball.addEventListener("touchstart",onBallTouchStart);
            ball.addEventListener("touchmove",onBallTouchMove);
            ball.addEventListener("touchend",onBallTouchEnd);
            moveBall(160, 160);
            var back = document.getElementById("ID_BACK");
            back.style.top = canvas.height - 30 + "px";
            back.addEventListener("touchstart", function() {
                document.location = ".";
            });
        }
    }
    //-->
</script>

</head>
<body onLoad="init()">

<canvas id="ID_CANVAS"></canvas>
<img id="ID_BALL" src="ball.png" style="position:absolute">
<img id="ID_BACK" src="back.png" style="position:absolute">
<!--<a id="ID_BACK" style="position:absolute" href=".">Back</a>-->

</body>
</html>

画像を引っ張って弾く

上下左右どのように引っ張っても本当にコレだけでイケた。
少しブレるかもと思ったけど一定速でスムースに動いてくれる。

目的どおりのものが少しずづできあがっていくのって楽しい!

JavaScript clearInterval

引っ張りゲーを実現するには指を放した後に引っ張った逆方向に一定時間画像を移動し続ける必要がある。
タイマーを利用するしかないだろう。

JavaScript では setInterval 関数を使う。
clearInterval していないサンプルコードが多い、動かしっぱなしはヤメようよ。

下記は 20/1000 秒毎に画像を移動、そして 1 秒でタイマーを停止してリセット。
タイマー毎に引っ張った距離の 1/5 ずつ戻す例にしてみた。
大抵のゲームは一定距離だけど、三角関数で計算するしかないだろうな…

それとタイマーを動かしている間は touchmove ハンドラ等を処理しないように。
ついポンポンとタッチすると酷いことになる。

記憶しておくデータが増えてとっ散らかるので構造体っぽくまとめている。
この記述方法は Python をやっている人ならすぐ解ると思う。
一つしか使わないなら static でもいいけど、その場合初期化されないので注意。
つまり下記では ballData.timer_id だと -1 にならないということです。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>画像を引っ張って弾く</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    margin: 0;
    overflow: hidden;
    -webkit-text-size-adjust: 100%;
}
#ID_CANVAS {
    background-color: #FFEE00;
}
</style>
<script type="text/javascript"><!--

    var canvas = null;
    var context = null;
    var ball = null;
    var offset = 24;
    // struct
    var ballData = function() {
        this.timer_id = -1;
        this.timer_count = 0;
        this.x = 0;
        this.y = 0;
        this.move_x = 0;
        this.move_y = 0;
    }
    var BallData = new ballData();

    // Event Handler
    var onBallTouchStart = function(e) {
        e.preventDefault();
    }
    var onBallTouchMove = function(e) {
        if (BallData.timer_id == -1) {
            context.clearRect(0, 0, canvas.width, canvas.height);
            BallData.x = e.touches[0].pageX;
            BallData.y = e.touches[0].pageY;
            context.beginPath();
            context.moveTo(160, 160);
            context.lineTo(BallData.x, BallData.y);
            context.stroke();
            moveBall(BallData.x, BallData.y);
        }
    }
    var onBallTouchEnd = function(e) {
        if (BallData.timer_id == -1) {
            BallData.move_x = (BallData.x - 160) / 5;
            BallData.move_y = (BallData.y - 160) / 5;
            BallData.timer_count = 50;
            BallData.timer_id = setInterval(onTimer, 20);
        }
    }
    // Timer Handler
    var onTimer = function() {
        if (BallData.timer_count > 0) {
            BallData.x -= BallData.move_x;
            BallData.y -= BallData.move_y;
            moveBall(BallData.x, BallData.y);
            BallData.timer_count--;
        } else {
            clearInterval(BallData.timer_id);
            BallData.timer_id = -1;
            context.clearRect(0, 0, canvas.width, canvas.height);
            moveBall(160, 160);
        }
    }
    // Func
    var moveBall = function(x, y) {
        ball.style.left = x - offset + "px";
        ball.style.top  = y - offset + "px";
    }
    // Connect
    var init = function() {
        if (window.TouchEvent) {
            canvas = document.getElementById("ID_CANVAS");
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight - 100;
            context = canvas.getContext("2d");
            ball = document.getElementById("ID_BALL");
            ball.addEventListener("touchstart",onBallTouchStart);
            ball.addEventListener("touchmove",onBallTouchMove);
            ball.addEventListener("touchend",onBallTouchEnd);
            moveBall(160, 160);
        }
    }
    //-->
</script>

</head>
<body onLoad="init()">
<div>
<canvas id="ID_CANVAS"></canvas>
<img id="ID_BALL" src="ball.png" style="position:absolute">
</div>
<br />
<p><a href=".">Back</a></p>
</body>
</html>

画像を引っ張って弾く

jsconsole

JavaScript コンソールって便利だよね。
もちろん本物の iPhone でも動いたよ。

次は画面隅で跳ね返る手段の実装か、面倒くさそうだ。
しかしずっとウチ姫型で作っているけどモンスト型のほうが需要あるんだろうな。

JavaScript Move Image

JavaScript の Canvas は基本的にデバイスコンテキストと同じ。
Canvas 上の画像を移動する場合は当然一度塗りつぶして新たな位置に再描写。
コレが基本。

しかし JavaScript は進化していた、レイヤーが使える。
つまりは img タグで指定された画像位置を動的に変更することが可能。

img タグの style に position:absolute を指定。
後はスクリプトで style.top 等の値を代入するだけというお手軽さ。
png 画像の透過属性も適用されたまま移動できる。

以下は半透明なボールのような画像をタッチすると移動できる例。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>画像をタッチして移動する</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    margin: 0;
    overflow: hidden;
    -webkit-text-size-adjust: 100%;
}
#ID_CANVAS {
    background-color: #FFEE00;
}
</style>
<script type="text/javascript"><!--

    var canvas = null;
    var context = null;
    var ball = null;
    var offset = 24;

    // Event Handler
    var onBallTouchStart = function(e) {
        e.preventDefault();
    }
    var onBallTouchMove = function(e) {
        context.clearRect(0, 0, canvas.width, canvas.height);
        var touch = e.touches[0];
        var x = touch.pageX;
        var y = touch.pageY;
        context.beginPath();
        context.moveTo(160, 160);
        context.lineTo(x, y);
        context.stroke();
        // Move Layer
        moveBall(x, y);
    }
    var onBallTouchEnd = function(e) {
        context.clearRect(0, 0, canvas.width, canvas.height);
        moveBall(160, 160);
    }
    var moveBall = function(x, y) {
        ball.style.left = x - offset + "px";
        ball.style.top  = y - offset + "px";
    }
    // Connect
    var init = function() {
        if (window.TouchEvent) {
            canvas = document.getElementById("ID_CANVAS");
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight - 100;
            context = canvas.getContext("2d");
            ball = document.getElementById("ID_BALL");
            ball.addEventListener("touchstart",onBallTouchStart);
            ball.addEventListener("touchmove",onBallTouchMove);
            ball.addEventListener("touchend",onBallTouchEnd);
            moveBall(160, 160);
        }
    }
    //-->
</script>

</head>
<body onLoad="init()">
<div>
<canvas id="ID_CANVAS"></canvas>
<img id="ID_BALL" src="ball.png" style="position:absolute">
</div>
<br />
<p><a href=".">Back</a></p>
</body>
</html>

画像をタッチして移動する

touch_move

パソコンとマウスでは JavaScript コンソールで iPhone Emulate で動いた。
まあ実際の端末のようにフリックでのアレはしませんけど。

touch 関連の addEventListener を img タグ位置にしてみた。
この方法なら画像の場所以外でのフリックは iPhone 標準になるね。
アレッと思う場合が減るのでこんな感じにすると良さげ。

透過属性が残ったままなのでラインが透けてみえるのが確認できる。
上手く利用すれば色々な Web アプリに応用できると思う。

注意しないといけないのは画像描写の原点は左上だということ。
上記のような場合は画像サイズの半分をオフセットして描写する。

なんだけど、何故か SO-02D では画像が中心に表示されないのだが。
オフセット無しなら中心に、iPhone 版 Chrome だと中心ってなんだこれ。
Android 全部でそうなのかは解らない、まだ調べること多いな…

追記: Ball を Bool と書いていた部分があったので書き換え、恥ずい…

window.innerWidth

3D を扱うスマホゲームの大半は Unity で作成されているようだ。
ウチ姫もその中の一つ、知らなかったけどもうスタンダードに近いのね。
Unity – Gallery

Linux に締め出された Mono がこんなところに生息していたとは。
ま、本格的に 3D ゲームを作りたくなったら手を出すか考えればいいだろう。
今は Web アプリのほうが伸びそうだし。

さて、ゲームというかグラフィックを表示するには画面サイズを得る必要がある。
JavaScript で横幅は screen.width と window.innerWidth がある。
スマホの横向きも考慮してどう違うのか確認。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>画面サイズ取得</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<script type="text/javascript"><!--

    var setText = function() {
        var br = "\<br /\>";
        // 画面はディスプレイサイズ、表示領域はクライアント領域
        // device-width 指定だとクライアントサイズは 320 になる
        var s = "画面 幅x高さ: " + screen.width + "x" + screen.height + br +
                "表示領域 幅x高さ: " + window.innerWidth + "x" + window.innerHeight + br;

        document.getElementById("ID_TEXT").innerHTML = s;
    }
    var init = function() {
        setText();
        // スマホ横向きで再取得
        // iPhone では横向きでも画面サイズは変わらないと確認できる
        window.onorientationchange = setText;
    }
    //-->
</script>

</head>
<body onLoad="init()">
<p id="ID_TEXT"></p>

<br />
<p><a href=".">Back</a></p>
</body>
</html>

すぐにスマホで確認できるようにテストページを作った。
JavaScript Test Page – L’Isola di Niente

スマホで viewport がこの指定だとクライアント幅は 320 になるようだ。
後は高さを都度取得して調節すれば問題なくグラフィック表示できそう。

window.onorientationchange ハンドラにて再取得で横向き時も得られる。
とやってみたら。

size_iphone

size_android

うーん、当然縦サイズが横サイズになるよね。
横向きでは使わないでね!が一番簡単(それでいいのか…

しかし iPhone は横にしても画面サイズは同じってなんでかな?
Android の画面サイズが解像度のままってのも変な感じ。
それとフォントサイズ、まだ調べなきゃいけないこと多いな。

どのブラウザでも動くのでなければ Web アプリの意味が無い。
でも IE はガン無視でいいと思う、タッチで使う人いなさそうだし。

lineTo

iPhone は本当に恐ろしい。

何が恐ろしいかというと、パソコンの電源を入れることがほとんどなくなった。
Fedora をデスクトップで使い更にこんなブログをやっている筆者が、である。

スマホで十分という人はパソコンでもたいしたことをしていない人だけ。
というのは思い込みです、Windows しか使えない人の妄想であると断言する。

今やパソコンを起動するのは動画を見る時とプログラミングをする時のみ。
いや短い Web 動画ならスマホで見る、本当に何かを作る時以外にパソコンは不要。
Xperia の時はこうはならなかったのに iPhone にしたとたんにこうだ。

直感的に使えるというウリ文句は嘘、だが少ない操作量で簡単に使えるし常に手元にある。
大型バイクのサブで 125cc スクーターを買ったらソッチにしか乗らなくなる現象と同じかも。
必要十分な速度で動いて手軽なほうを使うのは余程の理由がなければ当然かと。

もっと恐ろしいのは、、、ウチ姫。

ガールフレンド(仮)というゲームが少し話題になった時にブラウザ版という HTML5, JavaScript で動くバージョンがあると知り面白そうなので試した。
タッチで一行ずつ文字列を表示する JavaScript ネタを以前ココに書いたはず。
丁度そのころ同じ会社のウチ姫とコラボ企画をやっていたのでついでに試す。

結果、会社で絶賛流行中のパズドラにはピンとこなかった筆者がドハマり。

おかげでブログのネタが作れず三ヶ月近く更新が止まったことは内緒だよ。
少額とはいえ課金までしてしまう事態に、ドロシーちゃんカワイイよ。

uchihime

絶妙な位置を狙って弾くという行為がパチンコに似て…いや何でもない。
貫通したり分身したり巨大化したり、しまいにゃ波動砲を打ったりもするけど。
そういえば筆者はテトリスとかのパズルものが大の苦手だったな。

しかしウチ姫もブラウザ版が出ないものか、アメーバコインは iPhone から使えないし。
3D だから JavaScript では厳しいかな?

**********

長い前置きはこれくらいにして。
とにかく HTML5, JavaScript でウチ姫と似たようなことをやってみたい。

いきなり 3D では挫折が見えているので Canvas に描写してみる。
canvas のどこかをタッチして引っ張るとそこまで line を引くというように。

現在 iPhone でしか試していないけどなんとか上手くいったコード。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>タッチして引っ張る</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    margin: 0;
    overflow: hidden;
}
#ID_CANVAS {
    background-color: #FFFF00;
}
</style>
<script type="text/javascript"><!--

    var canvas = null;
    var context = null;
    var cx = 0;
    var cy = 0;

    // Event Handler
    var onTouchStart = function(e) {
        var touch = e.touches[0];
        cx = touch.pageX;
        cy = touch.pageY;
        // cancel the operation of default (iPhone)
        e.preventDefault();
    }
    var onTouchMove = function(e) {
        context.clearRect(0, 0, canvas.width, canvas.height);
        var touches = e.touches;
        for (var i=0; i<touches.length; i++) {
            var touch = touches[i];
            var x = touch.pageX;
            var y = touch.pageY;
            context.beginPath();
            context.moveTo(cx, cy);
            context.lineTo(x, y);
            context.stroke();
        }
    }
    var onTouchEnd = function(e) {
        context.clearRect(0, 0, canvas.width, canvas.height);
    }
    // Connect
    var init = function() {
        if (window.TouchEvent) {
            canvas = document.getElementById("ID_CANVAS");
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight - 100;
            canvas.addEventListener("touchstart",onTouchStart);
            canvas.addEventListener("touchmove",onTouchMove);
            canvas.addEventListener("touchend",onTouchEnd);
            context = canvas.getContext("2d");
        }
    }
    //-->
</script>

</head>
<body onLoad="init()">
<canvas id="ID_CANVAS"></canvas>

<br />
<p><a href=".">Back</a></p>
</body>
</html>

lineto

残念だけどパソコンのブラウザとマウスでは動かない、サーフェスは知らない。
後は sim 無し Xperia を充電しなきゃ。
そういえば Windows を前回起動したのはいつだったかな?

とにかく iPhone Safari 対策で preventDefault 呼び出しは必須みたい。
何かのソシャゲでツールバーが出なくて困ったけどコレをページ全体でやってたのか。

引っ張りゲーを作りたいのでマルチタッチは関係ないけどこんな面倒なコードになった。
ハンドラで Touch オブジェクトのリストを取得し for 文にする必要あり。
for in 文が使えないって面倒臭いな、何故メソッド列挙なんかにしたのだろう?

Canvas については Windows でデバイスコンテキストと同様と思っていい。
clearRect で以前の線を塗り潰し lineto にて新たに書き込まないと残ってしまう。

おまけで body の css で margin: 0; にしないとゼロ位置にならない。
今まで気が付かなかったけどそうなっていたんだね。

後は開始位置を固定して球を弾いてソレが障害物に当たると反射してそれから…
うおぉ先は長そうだ。

**********

筆者は local でこんなことをするので Linux というより Apache が必要なんだが。
何を作るでもない人であれば今の時代スマホがあれば本当にパソコンは必要ないかもね。
「パソコン?キモッ!」な反応をする人が増えることだけは間違いない。