月別アーカイブ: 2014年9月

document.createElement

さて、カエル王子を引っ張ってピヨ吉を退治する JavaScript だ。
こんな感じにしてみた。

※canvas サイズは 320×400 に固定
※ピヨ吉構造体を用意、HP や X,Y 座標メンバを保持
※ソレを画面に配置する数だけ配列にしておく
※タイマーでカエルが動く毎に for 文で全数チェック
※ピヨ吉にヒットしていれば HP を減らし 0 なら非表示にして通過する
※ピヨ吉すべての HP がゼロになればクリア

とりあえず二回ヒットするとピヨ吉が消える暫定コードに。
カエル王子は 32x32px にしてみたけど iPhone では少し引っ張り辛いかも。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ひよこを倒す 01</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: #FF7700;
}
</style>
<script type="text/javascript"><!--

    var canvas = null;
    var context = null;
    var ball = null;
    var piyos = [];
    // Constant
    const INIT_X = 160;  // X origin of the image
    const INIT_Y = 280;  // Y origin of the image
    const OFFSET = 16;   // Half of the image size
    const INTERVAL = 20; // n/1000 ms
    const LENGTH = 20;   // Distance the image moves
    const COUNT  = 250;  // INTERVAL*n ms
    // struct
    var piyokichi = function(x, y, hp) {
        this.x = x;
        this.y = y;
        this.hp = hp;
        this.img = null;
    }
    var ballData = function() {
        this.timer_id = -1;
        this.timer_count = 0;
        this.x = 0;
        this.y = 0;
        this.rotation = 0;
    }
    var BallData = new ballData();

    /*
     * Touch 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;
            if (BallData.y > 400 - OFFSET)
                BallData.y = 400 - OFFSET;
            context.beginPath();
            context.moveTo(INIT_X, INIT_Y);
            context.lineTo(BallData.x, BallData.y);
            context.stroke();
            moveBall(BallData.x, BallData.y);
        }
    }
    var onBallTouchEnd = function(e) {
        if (BallData.timer_id == -1) {
            BallData.rotation = Math.atan2(BallData.y - INIT_Y, BallData.x - INIT_X)
            BallData.timer_count = COUNT;
            BallData.timer_id = setInterval(onTimer, INTERVAL);
            context.clearRect(0, 0, canvas.width, canvas.height);
        }
    }
    /*
     * Timer Handler
    **/
    var onTimer = function() {
        // Clear ?
        var clear = piyos.length;
        for (var i=0; i<piyos.length; i++) {
            if (piyos[i].hp == 0) clear -= 1;
        }
        if (clear == 0) {
            context.font = "64pt 'Arial'";
            context.fillText("Great!", 10, 100);
            clearInterval(BallData.timer_id);
            BallData.timer_id = -1;
            return;
        }
        if (BallData.timer_count > 0) {
            // Hit the Rival ?
            if (!getRivalHit()) {
                // Hit the Wall ?
                if (BallData.x < OFFSET || BallData.x > canvas.width - OFFSET)
                    BallData.rotation = Math.PI - BallData.rotation;
                if (BallData.y < OFFSET || BallData.y > canvas.height - OFFSET)
                    BallData.rotation = -BallData.rotation;
            }
            // Move Image
            BallData.x -= Math.cos(BallData.rotation) * LENGTH;
            BallData.y -= Math.sin(BallData.rotation) * LENGTH;
            moveBall(BallData.x, BallData.y);
            BallData.timer_count--;
        } else {
            clearInterval(BallData.timer_id);
            BallData.timer_id = -1;
            moveBall(INIT_X, INIT_Y);
        }
    }
    /*
     * Hit Function
    **/
    var getRivalHit = function() {
        for (var i=0; i<piyos.length; i++) {
            if (BallData.x > piyos[i].x - OFFSET &&
                BallData.x < piyos[i].x + OFFSET &&
                BallData.y > piyos[i].y - OFFSET &&
                BallData.y < piyos[i].y + OFFSET) {
                // HP == 0?
                piyos[i].hp -= 5;
                if (piyos[i].hp <= 0) {
                    piyos[i].hp = 0;
                    piyos[i].img.style.display = "none";
                    continue;
                }
                if (BallData.x > piyos[i].x - OFFSET && BallData.x < piyos[i].x + OFFSET)
                    BallData.rotation = Math.PI - BallData.rotation;
                if (BallData.y > piyos[i].y - OFFSET && BallData.y < piyos[i].y + OFFSET)
                    BallData.rotation = -BallData.rotation;
                return true;
            }
        }
        return false;
    }
    /*
     * Move Function
    **/
    var moveBall = function(x, y) {
        ball.style.left = x - OFFSET + "px";
        ball.style.top  = y - OFFSET + "px";
    }
    /*
     * Initialize
    **/
    var init = function() {
        if (window.TouchEvent) {
            canvas = document.getElementById("ID_CANVAS");
            canvas.width = window.innerWidth;
            canvas.height = 400;
            context = canvas.getContext("2d");
            ball = document.getElementById("ID_BALL");
            ball.addEventListener("touchstart",onBallTouchStart);
            ball.addEventListener("touchmove",onBallTouchMove);
            ball.addEventListener("touchend",onBallTouchEnd);
            moveBall(INIT_X, INIT_Y);
            // Back Button
            var back = document.getElementById("ID_BACK");
            back.style.top = canvas.height + "px";
            back.addEventListener("touchend", function() {
                document.location = ".";
            });
            // Create Piyokichi
            piyos.push(new piyokichi(20, 200, 10));
            piyos.push(new piyokichi(50, 30, 10));
            piyos.push(new piyokichi(60, 100, 10));
            piyos.push(new piyokichi(170, 80, 10));
            piyos.push(new piyokichi(280, 120, 10));
            for (var i=0; i<piyos.length; i++) {
                var img = document.createElement("img");
                img.alt = "image";
                img.src = "kaeru01/hiyo02_32x32.gif";
                img.width = 32;
                img.height = 32;
                img.style.position = "absolute";
                img.style.left = piyos[i].x - OFFSET + "px";
                img.style.top  = piyos[i].y - OFFSET + "px";
                document.body.appendChild(img);
                piyos[i].img = img;
            }
        }
    }
    //-->
</script>

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

<canvas id="ID_CANVAS"></canvas>
<img id="ID_BALL" src="kaeru01/kaeru02_32x32.gif" style="position:absolute" alt="ball">
<img id="ID_BACK" src="back.png" style="position:absolute" alt="back">

</body>
</html>

kaeru01

ひよこを倒す 01

敵の攻撃やターン数、障害物その他についてはまた今度。
長くなってきたので次回から[リンク先で Ctrl+U してね!]にするつもり。

他に今回やってみたこと。

canvas に文字列を表示するには fillText
[MS Pゴシック]なんてこのスマートフォン時代に指定しないで下さい。

context.font = "64pt 'Arial'";
context.fillText("Great!", 10, 100);

img タグを動的に作るには createElement
その img を非表示にするには img.style.display = “none”;

var img = document.createElement("img");
document.body.appendChild(img);

後は前回までのコードを少し応用しただけでココまで作れた。
しかしヒット判定は四方チェックしてから振り分けしか手段が無いのかな?
ネットに転がっているブロック崩しのコードを色々見るもイマイチ参考にならないし。
全部グローバル変数で読み辛い人多過ぎだし、構造体やクラスって凄く便利だよ。

今回は[フリー素材 動物アイコン]で適当に検索して以下をお借りしました。
1キロバイトの素材屋さん-無料素材の配布/可愛いアイコン・動く顔文字などのフリー素材集-

カエルとひよこがあって丁度よかった、32x32px に変更しただけ。
本格的に作る時があったらオリジナルキャラにしますが筆者は絵心が…

Girlfriend

スマホ学園恋愛ゲーム「ガールフレンド(仮)」からPC向けのブラウザ版がリリース – ねとらぼ

girlfrend_pc

Linux 版 Chrome でも普通にログインできた。
レベルが結構進んでいたり HR クロエをコンプしているのは気にしないで。
ゲームは全然面白くないけど[ウチ姫]と頻繁にコラボするからヤメられない。

ちなみにコイツに課金する気は皆無なので SR がちっとも当たらないw
部活(という名のギルド)に入る気もないし横取りイベントとかは完全不参加w
たまにくれるコインは sim 無し Xperia を起動してウチ姫のガチャに使うw
それでも HR クロエ等はコンプできるしまあいいかと。

rorikaguya

ちなみに少し前のコラボでもらった月のカケラでロリカグヤをゲット。
ロリィナが来なかったので称号クエストはソコソコにアリスを集めているけど。
水 SR が何故か全然当たらないのでシンディ取りたいけど進化無理っぽいんで。

しかしランキングを見るとロリィナだらけで引く、みなドンダケ課金してんの?
ウチ姫の知名度でこれだとパズドラやモンストはいったい…
ってこれは関係ないね。

YouTube とかを見れば解るけど JavaScript コンソールでエミュレートすれば実は以前から可能だったというオチがありますが。
せっかく普通にパソコンで見れるので Ctrl+U して中身を見てみよう。

gf_320px

viewport 指定はお約束どおり。
パソコン版も見事に css にて 320px に指定しているんだね。
スゲェちいさいけどスマートフォンとつじつまを合わせるにはやはりこれしかないか。

buttle

バトルシーンや劇場等のスクロールしない画面は 320×400 になっているっぽい。
しかし女の子画像を「名前を付けて保存」すると 640×800 になる。
Android and iPhone word-break | PaePoi
max-width: 100%; で縮小表示しているようだ。

トップページはリロード毎に女の子の画像とセリフが変わる。
試すと毎回ランダムに読み込んでいるので Java か PHP だろうね。

他にも色々と参考になったけど後は各自で。
簡単にソースを参考にできる環境にしてくれた運営に感謝。

面白くないとケナしておいて参考にする私もアレだと思うけど。
ウチ姫面白いです、で許してください。

GtkMenuButton F10

Y901x 1.1.2 公開、日は変わってしまったけど。

y901x112

メニューバーを取り払ったよ。
ラジオメニューじゃ解りづらい再生速度アスペクト比切り替えもステータスバーに移した。

GtkHeaderBar and GStreamer | PaePoi
Mini GtkButton | PaePoi

つーか、今頃になってやっと上記を実装した、半年も前だったのか。
使い道の例だと思ってください、多分作った本人しか使っていないアプリなので。

所で F10 キーでメインメニューがドロップするのは知っているよね。
GtkMenuButton ではこれを自分で実装しないといけないみたい。
こんな感じでいいみたい、F10 ってシステム管理じゃなかったんだ。
ちょっと具体的に追記

#!/usr/bin/env python3

class Win(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # GtkAccelGroup
        accelgroup = Gtk.AccelGroup.new()
        self.add_accel_group(accelgroup)
        # Preference button
        preferencebutton = Gtk.MenuButton.new()
        image_gear = Gtk.Image.new_from_icon_name("emblem-system-symbolic", Gtk.IconSize.MENU)
        preferencebutton.set_image(image_gear)
        # Set F10 Accel
        preferencebutton.add_accelerator("clicked", accelgroup, Gdk.KEY_F10, 0, Gtk.AccelFlags.VISIBLE)
        # HeaderBar
        headerbar = Gtk.HeaderBar()
        headerbar.pack_end(preferencebutton)

ついでに今頃気が付いた。
Alt+F10 で最大化切り替えできる、これは便利だ。
まあシステム設定で変更できるんだけど。

後 Y901 伝統の Z キーでフルスクリーンは実装できなかった。
一応強引な手段もあるけど GNOME デフォルトの F11 のみでいいかなと。

次は Alt+Enter でプロパティ表示を実装したいな。
一度弄り始めると急に色々思い付くんだよね。

Ubuntu での動作確認は明日やろう(ぉい!
個人的には超有名な某ゲームエンジンと同じ名前であるあの糞デスクトップ環境はガン無視したいんだけどユーザー数がなぁ…

GtkToggleButton and keyboard

GtkToggleButton のシグナルには activate と clicked がある。

activate は On 時のみ、clicked は On/Off 両方シグナルが発生する。
マウス等でクリック時だけでなく gtk_button_clicked() 関数にも反応する。

なので GtkToggleButton を Off させる場合に困る場合がある。
トグルなので二度押しで上るわけだが複数ボタンで排他制御を行う場合に。
更にキーボードでも同様に動作するようにとなると…

activate では二度押しで上るのを検知できない。
clicked では排他で上るボタンからもシグナルが飛んでくる。

activate は使えそうもないので clicked にするしかない。
Off 検知を逃すのとキーボードでつじつまを合わせるのに一苦労。
そのためアクロバチックなコードを今まで書いていた。

class Y901window(Gtk.ApplicationWindow):
    def __init__(self, app):
        #
        # etc...
        #
        self.toolbox.one.connect("clicked", self.on_rep_btn, 1)
        self.toolbox.all.connect("clicked", self.on_rep_btn, 2)
        self.toolbox.rdm.connect("clicked", self.on_rep_btn, 3)
        #
        # etc...
        #

    def on_keydown(self, accelGroup, window, keyval, modifier):
        if keyval == Gdk.KEY_F7:
            self.toolbox.one.clicked()
        elif keyval == Gdk.KEY_F8:
            self.toolbox.all.clicked()
        elif keyval == Gdk.KEY_F9:
            self.toolbox.rdm.clicked()
        ''' old
        if keyval == Gdk.KEY_F7:
            self.on_rep_btn(None, 1)
        elif keyval == Gdk.KEY_F8:
            self.on_rep_btn(None, 2)
        elif keyval == Gdk.KEY_F9:
            self.on_rep_btn(None, 3)'''

    def on_rep_btn(self, widget, num):
        if widget.get_active():
            if self.settingwin.repeat != num:
                self.toolbox.one.set_active(num == 1)
                self.toolbox.all.set_active(num == 2)
                self.toolbox.rdm.set_active(num == 3)
                self.settingwin.repeat = num
        else:
            if not self.toolbox.one.get_active() and not self.toolbox.all.get_active() and not self.toolbox.rdm.get_active():
                self.settingwin.repeat = 0
        ''' old
        if widget == None:
            if num == 0:
                self.toolbox.one.set_active(False)
                self.toolbox.all.set_active(False)
                self.toolbox.rdm.set_active(False)
            elif num == 3:
                self.toolbox.one.set_active(False)
                self.toolbox.all.set_active(False)
                self.toolbox.rdm.set_active(True)
            elif num == 2:
                self.toolbox.one.set_active(False)
                self.toolbox.all.set_active(True)
                self.toolbox.rdm.set_active(False)
            elif num == 1:
                self.toolbox.one.set_active(True)
                self.toolbox.all.set_active(False)
                self.toolbox.rdm.set_active(False)
        else:
            if widget.get_active():
                if self.settingwin.repeat != num:
                    action = self.actiongroup.get_action(ac2_str[num])
                    action.set_current_value(num)
                    if num != 3:
                        self.toolbox.rdm.set_active(False)
                    if num != 2:
                        self.toolbox.all.set_active(False)
                    if num != 1:
                        self.toolbox.one.set_active(False)
            else:
                if self.settingwin.repeat == num:
                    action = self.actiongroup.get_action(ac2_str[0])
                    action.set_current_value(0)
                    self.toolbox.one.set_active(False)
                    self.toolbox.all.set_active(False)
                    self.toolbox.rdm.set_active(False)'''

最初に書いたとおり gtk_button_clicked() 関数にも反応だからコッチを使う。
これでマウスクリックと直接呼び出しとを振り分けする必要はなくなった。

排他を if 文で振り分けしていた部分はこの手があった。
今まで何をやってきたのかと情け無くなった。

排他トグルで All Off は All Off の場合のみ処理すればいい。
それ以外だったら必ずどこかが On になっているということだから。
散々悩んだことなのに解ってしまえばこんなにアッサリ。

ということで、こんなにコードが短くなった。
強引に短くするドヤ顔コードは吐き気がするけど正攻法で短くするのは気持ちいい。

GtkActionGroup の処理が取り除かれているけど気にしないで。
メニューバーを GNOME の意向に合わせ取っ払っているだけなので。
iPhone を使うようになってから更にメニューバーが邪魔に思えてきた。
何故あんなのが普及したのか謎と考えるくらいに。