ジャンル雑多なゲーム・ゲーム制作関連の色々な情報を取り扱っているブログ。最近はBlenderについてが中心。
[phina.js] マウス・タッチ・キー入力取得 マルチタッチ入力(touchList・pointers)編

[phina.js] マウス・タッチ・キー入力取得 マルチタッチ入力(touchList・pointers)編

今回の記事は、phina.jsでのマルチタッチ入力の取得方法についてを書いていきたいと思います。他にも入力取得に関する記事を書いておりますので、よろしければご覧ください。

関連記事:

もしphina.jsが分からない方は、[phina.js]基本 — テンプレートについてをご一読いただくことをおすすめいたします。

※2018年10月18日 加筆・修正を行いました。
※2020年9月11日 加筆・修正を行いました。

目次

準備

コーディングは、Runstantや、またはRunstant liteを使って試されることをおすすめします。

一応雛型として置いておきます。

<script src='https://cdn.jsdelivr.net/gh/phi-jp/phina.js@0.2.3/build/phina.js'></script>
// グローバル領域に展開
phina.globalize();

/*
 * メインシーン
 */
phina.define("MainScene",{
  // 継承
  superClass:"DisplayScene",
  // コンストラクタ
  init: function(){
    // 親クラスの初期化
    this.superInit();
    // ここに処理を書く
  },
  // 更新
  update: function(){
  
  },
});

/*
 * メイン処理
 */
phina.main(function(){
   // アプリケーションを生成
   const app = GameApp({
     // MainSceneから開始
     startLabel: "main",
   });
   // fps表示
   //app.enableStats();
   // 実行
   app.run();
});

TouchListとpointers

ここでも書いている通り、マルチタッチ入力は、touchListや、pointersを呼び出して取得します。どちらも、TouchListクラスから参照されているので、TouchListクラスについて読み解きながら書いていこうと思います。

プロパティ

プロパティ名説明デフォルト値
domElementCanvas参照domElement
touchMapタッチ入力の情報が入る{}
touchesタッチ入力の情報が配列で入る。pointersにはこれが入っている。[]
_id各入力情報管理。32ビット周期でIDをループさせるnew Unit32Array(1)

補足:touchMapとtouchesの違いは全ては把握していませんが、
dx、dyやfx、fyなどの値が、touchesからは取得できますが、
touchMapからは取得できませんでした。

メソッド

getEmpty()
タッチの情報を取得できます。
タッチ入力があったとき、
これが呼び出されて各プロパティにタッチ入力の情報を格納しているようです。
特に直接触らなくて良いと思います。
getTouch(id)
タッチ入力を取得できます。

[引数]
id: 取得したい入力が何番目かを数値で指定します。

removeTouch(touch)
タッチ入力の情報を消せます。
使ってみたのですが、
全部のタッチ入力を一度やめないと、指定した以外のタッチ入力の情報が更新されなくなってしまったので、正しい使い方が他にあるのかもしれないですが、おススメできません。

[引数]
touch: 消したいタッチ入力が何番目かを数値で指定します。

補足:何番目というのは、
その時点で同時にされている入力が、
入力が開始された順に
最初が0でその後、1、 2、 3…というように格納されています。
一度タッチをやめると、その入力情報は消えます。

pointersまたはtouchList.touchesに格納されている配列の各要素に設定されているプロパティ

プロパティ名説明デフォルト値
releasedタッチ入力がされているかの判定が真偽値が入る。おそらく、入力がある時は空、入力がなくなったらtrue。 
flagsこちらもタッチ入力がされているかの判定が、数値で入っている。入力がある時1、終わった時0が入っている。0

補足:どちらも、入力が終わった瞬間を取得するのに使われているプロパティの様です。
何も入力がない通常時、touchesの要素ごと存在していないので、注意。

サンプル

ソースコード

/*
 * メインシーン 
 */
phina.define("MainScene", {
    superClass: "DisplayScene",
    init: function () {
        this.superInit();
        // 背景色
        this.backgroundColor = "black";

        // 電気の各ビリビリの先端のグループ
        this.elcGroup = DisplayElement().addChildTo(this);

        // 電気のコアやビリビリの生成
        this.createElc();
    },
    // 更新
    update: function (app) {
        // タッチ入力を取得
        const p = app.pointers;
        
        // ビリビリの数カウント
        let count = 0;

        this.elcGroup.children.each((e) => {
            // タッチ入力判定
            switch (p.length) {
                case 0:     // タッチ入力なし
                    // ビリビリの先端の座標を更新
                    e.x = Math.cos(Math.PI * e.rx / 180) * 320 + 320;
                    e.y = Math.sin(Math.PI * e.ry / 180) * 480 + 480;
                    // 画面の外へ出ないように制御
                    e.x = Math.max(Math.min(e.x, 640), 0);
                    e.y = Math.max(Math.min(e.y, 960), 0);
                    // ビリビリの先端の移動位置を更新
                    e.rx += e.rx % 20;
                    e.ry += e.rx % 20;
                    // 一定フレーム数経過後、ビリビリの先端の座標や移動位置を変更
                    if (app.frame % 35 === 0) {
                        e.rx = Math.randint(0, 360);
                        e.ry = Math.randint(0, 360);
                        e.x = (Math.randint(0, 640));
                        e.y = (Math.randint(0, 960));
                    }
                    break;
                case 1:     // タッチ入力1ヶ所
                    if (p[0].className === 'phina.input.Mouse') {   // インプットのタイプがマウスだった場合
                        // ビリビリの先端の座標や移動位置をバラけさせる
                        e.x = (Math.randint(0, 640));
                        e.y = (Math.randint(0, 960));
                        break;
                    }
                    e.x = p[0].x;
                    e.y = p[0].y;
                    if (p[0].released) {// タッチ入力が終わった時
                        // ビリビリの先端の座標や移動位置をバラけさせる
                        e.x = (Math.randint(0, 640));
                        e.y = (Math.randint(0, 960));
                    }
                    break;
                case 2:
                    // ビリビリのカウント数によって
                    // タッチ入力のあった場所に均等に振り分ける
                    e.x = p[count % 2].x;
                    e.y = p[count % 2].y;
                    break;
                case 3:
                    e.x = p[count % 3].x;
                    e.y = p[count % 3].y;
                    break;
                case 4:
                    e.x = p[count % 4].x;
                    e.y = p[count % 4].y;
                    break;
                case 5:
                    e.x = p[count % 5].x;
                    e.y = p[count % 5].y;
                    break;
                default:
                    // 5以上の入力があった場合、
                    // 5番目以降の入力は無視
                    e.x = p[count % 5].x;
                    e.y = p[count % 5].y;
                    break;
            }
            // カウントを増やす
            count++;

            // ビリビリの先端から画面中心までのベクトル
            let vec = Vector2(e.x - 320, e.y - 480);
            // 角度
            let deg = vec.toDegree();

            // ビリビリの線の更新
            const paths = [];
            // ビリビリの線の画面中心部につなげる座標
            let x = Math.floor(Math.cos(Math.PI * deg / 180) * (Math.abs(vec.x) / 320 * 100));
            let y = Math.floor(Math.sin(Math.PI * deg / 180) * (Math.abs(vec.y) / 480 * 100));
            paths.push(Vector2(320 + x, 480 + y));

            // 途中の座標
            const c = Math.cos(Math.PI * (deg + Math.randint(-45, 45)) / 180);
            const s = Math.sin(Math.PI * (deg + Math.randint(-45, 45)) / 180);
            for (let i = 0; i < 4; i++) {
                x = Math.floor(c * (Math.abs(vec.x) / 320 * Math.randint(110 + i * 50, 110 + (i + 1) * 50)));
                y = Math.floor(s * (Math.abs(vec.y) / 480 * Math.randint(110 + i * 90, 110 + (i + 1) * 90)));
                paths.push(Vector2(320 + x, 480 + y));
            }
            // ビリビリの先端の座標
            paths.push(Vector2(e.x, e.y));

            // ビリビリの線の更新
            e.path.setPaths(paths);
        });
    },
    // 電気のコアやビリビリの生成
    createElc: function () {
        // コア
        CircleShape({
            shadow: "hsla(350,100%,90%,1)",
            shadowBlur: 100,
            radius: 100,
            fill: "white",
            stroke: null,
            padding: 100,
            x: 320,
            y: 480
        }).addChildTo(this);

        (15).times(() => {
            // ビリビリの先端
            const circle = CircleShape({
                radius: 0,
                fill: "hsla(350,100%,90%,1)",
                stroke: null,
                x: Math.randint(0, 640),
                y: Math.randint(0, 960)
            }).addChildTo(this.elcGroup);

            // ビリビリの先端の移動用変数
            circle.rx = Math.randint(-20, 20);
            circle.ry = Math.randint(-20, 20);

            // ビリビリの線
            circle.path = PathShape({
                strokeWidth: 7,
            }).addChildTo(this);
            const grad = this.canvas.context.createRadialGradient(320, 480, 0, 320, 480, 480);
            grad.addColorStop(0, "hsla(300,100%,85%,1.0)");
            grad.addColorStop(0.6, "hsla(240,100%,85%,1.0)");
            grad.addColorStop(1, "hsla(300,100%,85%,1.0)");
            circle.path.stroke = grad;

            // 光らせる
            circle.blendMode = "lighter";
            circle.path.blendMode = "lighter";
        });
    }
});

電気のコアやビリビリの生成の時に、ビリビリの線用のPathShapeを、ビリビリの先端用のCircleShapeにaddChildToで子オブジェクトとしてセットしても良かったのですが、座標の計算が面倒だったのでやめました。PathShapeについては、以前書いてみた記事がありますのでよろしければこちらをご覧ください。

また、pointersは、マウスの入力も取得してしまうようで、画面上をオーバーさせるだけでも反応する(しかもカーソル外れても入力残る)ようだったので、それにちょっと悪戦苦闘しました。p[0].classNameでインプットのクラスの種類を調べられたので、マウスで入力された時にはこれを使って反応を回避していますが…、

if (p[0].className === 'phina.input.Mouse') {   // インプットのタイプがマウスだった場合
   // ビリビリの先端の座標や移動位置をバラけさせる
   e.x = (Math.randint(0, 640));
   e.y = (Math.randint(0, 960));
   break;
}

実は、入力が残ってしまっていて、どうにも消せなかったので、ビリビリの先端の位置を乱数で動かしてごまかしてます(^_^;)。なのでビリビリの動きが、通常時やタッチ入力終了後と、マウス入力後で違ったりします。(多分もっといい方法とか入力消す方法あると思います)

他にも入力取得に関する記事を書いておりますので、よろしければこちらもご覧ください。

ご意見・ご感想、また、ここ間違ってるよとか、もっといい方法あるよといったご指摘などございましたら、お手数ですがコメント欄や当twitterアカウントへお願いしますm(_ _)m

参考にさせていただいたサイト・ページ一覧

Pocket