ブロックチェーンゲーム制作チェーン -Minimal Arcade Medal-

ブロックチェーン技術、ゲーム作成のためのコード、ドット絵やアニメーションの作り方を解説するブログです。最終的に記事の内容を組み合わせることで誰でもブロックチェーンゲームが作れるようにしたいです。Micro Arcade MedalというdApps開発中です。

CocosCreatorでタッチしたところにプレイヤーを動かす方法

f:id:kusakabob:20180708131044p:plain


CocosCreator のサンプルプロジェクトより


f:id:kusakabob:20180708102456p:plain


ここのプログラムを解説します!
場所は以下の通り。
example/assets/cases/03_gameplay/01_player_control/onTouchCtrl


f:id:kusakabob:20180708101352p:plain


このプログラムは画面をタッチすると緑のモンスターがタッチした箇所に動くというものです。


具体的には、

  1. タッチが発生した座標をとる
  1. 現在のプレイヤーの座標をとる
  1. 画面を押している間、どの方向にどれだけ進むかを計算する
  1. 上記のイベントを適用して、プレイヤーの座標を更新する


といったプロセスです。

OnTouchCtrl.js全文

const i18n = require('i18n');

cc.Class({
    extends: cc.Component,

    properties: {
        canvas: cc.Node,
        touchLocationDisplay: {
            default: null,
            type: cc.Label
        },
        follower: {
            default: null,
            type: cc.Node
        },
        followSpeed: 200
    },

    // use this for initialization
    onLoad: function () {
        var self = this;
        self.moveToPos = cc.p(0, 0);
        self.isMoving = false;

        //addEventListener
        self.canvas.on(cc.Node.EventType.TOUCH_START, function (event) {
            var touches = event.getTouches();
            var touchLoc = touches[0].getLocation();
            self.isMoving = true;
            self.moveToPos = self.follower.parent.convertToNodeSpaceAR(touchLoc);
            self.touchLocationDisplay.textKey = i18n.t("cases/03_gameplay/01_player_control/On/OnTouchCtrl.js.1") + Math.floor(touchLoc.x) + ', ' + Math.floor(touchLoc.y) + ')';
        }, self.node);

        self.canvas.on(cc.Node.EventType.TOUCH_MOVE, function (event) {
            var touches = event.getTouches();
            var touchLoc = touches[0].getLocation();
            self.moveToPos = self.follower.parent.convertToNodeSpaceAR(touchLoc);
            self.touchLocationDisplay.textKey = i18n.t("cases/03_gameplay/01_player_control/On/OnTouchCtrl.js.1") + Math.floor(touchLoc.x) + ', ' + Math.floor(touchLoc.y) + ')';
        }, self.node);
        self.canvas.on(cc.Node.EventType.TOUCH_END, function (event) {
            self.isMoving = false; // when touch ended, stop moving
        }, self.node);
    },

    // called every frame
    update: function (dt) {
        if (!this.isMoving) return;
        //current pos to cal newPos
        var oldPos = this.follower.position;
        // get move direction
        var direction = cc.pNormalize(cc.pSub(this.moveToPos, oldPos));
       
        // multiply direction with distance to get new position
        var newPos = cc.pAdd(oldPos, cc.pMult(direction, this.followSpeed * dt));
        // set new position
        this.follower.setPosition(newPos);
    }
});


そんな長くないので上から見て行きましょう。

Property

const i18n = require('i18n');


まずこれは多言語化をサポートしてくれるモジュールです。後ほど外部テキストデータを読み込むためにここで読み込んでます。

properties: {
        canvas: cc.Node,
        touchLocationDisplay: {
            default: null,
            type: cc.Label
        },
        follower: {
            default: null,
            type: cc.Node
        },
        followSpeed: 200
    },


ここではpropertiesを定義してます。定義されてるのは以下。

  • canvas:後ほどEventくっつけるためのNodeクラス参照。
  • touchLocationDisplay:タッチしてる座標を表示するノードへの参照。
  • follower:緑モンスター表示するノードへの参照。
  • followSpeed:どのくらいの速さで緑モンスターが動くか。

onLoad

 onLoad: function () {
        var self = this;
        self.moveToPos = cc.p(0, 0);
        self.isMoving = false;

        //addEventListener
        self.canvas.on(cc.Node.EventType.TOUCH_START, function (event) {
            var touches = event.getTouches();
            var touchLoc = touches[0].getLocation();
            self.isMoving = true;
            self.moveToPos = self.follower.parent.convertToNodeSpaceAR(touchLoc);
            self.touchLocationDisplay.textKey = i18n.t("cases/03_gameplay/01_player_control/On/OnTouchCtrl.js.1") + Math.floor(touchLoc.x) + ', ' + Math.floor(touchLoc.y) + ')';
        }, self.node);

        self.canvas.on(cc.Node.EventType.TOUCH_MOVE, function (event) {
            var touches = event.getTouches();
            var touchLoc = touches[0].getLocation();
            self.moveToPos = self.follower.parent.convertToNodeSpaceAR(touchLoc);
            self.touchLocationDisplay.textKey = i18n.t("cases/03_gameplay/01_player_control/On/OnTouchCtrl.js.1") + Math.floor(touchLoc.x) + ', ' + Math.floor(touchLoc.y) + ')';
        }, self.node);
        self.canvas.on(cc.Node.EventType.TOUCH_END, function (event) {
            self.isMoving = false; // when touch ended, stop moving
        }, self.node);
    },


ここでのthisはonTouchCtrlです。このComponent自身ですね。
moveToPosには2D ベクトル、つまり座標が入ります。
isMovingはfalse、タッチされてないときはupdate止めるために使われます。


次にself.canvas.on何ですが、addEventと同義です。
引数が3つあって、(イベントの種類、イベント、イベント発生対象)です。
今回はタッチしたとき、このコンポーネントがpropertyとして持ってるnodeが以下の関数実行しますってことです。


関数内ではまずtouchされた座標を獲得。touchされてるので動いてるtrue。
self.follower.parentはCanvasノード。touchされた座標をcanvasのAnchor Point(0.5,0.5)からみてどこにあるのかって表現に変換してます。
そして、座標を表示するノードのテキストを更新するんですが、前述のi18nが出てきます。国別ファイルからテキスト持ってくると同時にタッチされた座標突っ込んでます。


あとはタッチが動いた時の処理と終わった時の処理ですが、ほぼ同じっていうのと動いてるかどうかを切り替えてるだけなんで割愛。

update

 update: function (dt) {
        if (!this.isMoving) return;
        //current pos to cal newPos
        var oldPos = this.follower.position;
        // get move direction
        var direction = cc.pNormalize(cc.pSub(this.moveToPos, oldPos));
       
        // multiply direction with distance to get new position
        var newPos = cc.pAdd(oldPos, cc.pMult(direction, this.followSpeed * dt));
        // set new position
        this.follower.setPosition(newPos);
    }

これで最後です!
updateはゲームループですね。dtでインターバル(秒)が見れます。
タッチされてなかったら、updataやめる!
oldPosに現在の座標を保存します。


でここが一番難しいとこだと思うんですが、
一番内側から見ていくと、まずタッチされた座標と現在の座標の差分を取ってます。
そしてpNormalizeしてるんですが、ここは色々調べたんですが、Vectorのmagnitudeを1にしているようです。
VectorのmagnitudeはVectorの長さのようで、算出された角度を変えないでそれを1にしてます。
1にすると何が嬉しいのかというと、ベクトルの角度を1単位として扱えるってことです。
これに進む距離をかけるとどの方向にどれだけ長さ進んだのかを出せます。


そして次にフレームごとに進む距離を計算してます。pMultで先ほど出した長さ1のベクトルにSpeed*time=distanceをかけてます。
これでどの角度にどれだけ進むのかってのがわかります。そしてその値を現在の値に足すことで今いる場所からの相対的な移動座標がわかります。


緑モンスターの位置を移動座標にセットして終わりです。
これでタッチされた場所めがけてモンスターが動いていきます。

所感

現在モンスターがいる座標からタッチされた座標への角度を算出してそれを1単位として距離をかけるってところが肝ですね。

これでビームをタッチした方向に飛ばすことができますね。
僕は昔これが実装できずに2日間くらい試行錯誤した挙句諦めました笑
リベンジできてよかった〜。