(ゲームイメージ)

別窓表示

武器を近接攻撃に変える

ポッと出のリンゴで敵を撃破するのではなく、剣で撃破するように変えてみます。今回は説明が一足飛びですが、慣れない間はゆっくりソースを何度も見直してみてください。

攻撃をする、とは

今までいつ攻撃ができていたか、攻撃をした時なにが起こっていたのかを思い出してみます。

  • 攻撃できた時
    • クリック時
  • 起こっていた事
    • リンゴが出た

特徴的なのはリンゴです。クリックする時だけ「現れる」ということは恐らく新しくオブジェクトを作る事が必要で、つまり、new Appleと書かれているあたりをみてみると、クリック時の動作を推測できそうです。

そう思って再びソースコードをみると、new Apple、ここに出てきました。

game.rootScene.on('touchstart', function(evt){
    player.y = evt.localY;    // set position to touch-y position
    var apple = new Apple();
});

ということは、ここの'touchstart'が、クリック時の動作を示しているのでしょう。タッチスタート…ということは、マウスを押し始めたとき、ということでしょう。

今、touchstart時にしたいのはリンゴを生み出すのではなく、プレイキャラクターのグラフィックを攻撃しているものに変えることです。ですので、まずはこれを変えてみましょう。

プレイキャラクターのグラフィックを変える

では、プレイキャラクターの定義はどこでしていたでしょうか。

// make new class for player
var Player = enchant.Class.create(enchant.Sprite, {
    initialize: function(){
        enchant.Sprite.call(this, 32, 32);
        this.image = game.assets['chara7.png'];
        this.x = 288;// add:右側に表示。320-32の位置
        this.frame = 10;                   // set image data
        game.rootScene.addChild(this);     // add to canvas
    }
});

ここです。ところで今更ですが、これはあまり見ない形を取っていますよね。文法もどきの時間でも特にぴったりな説明はしませんでした。

落ち着いて見てください。セミコロンを利用した文法はゆるっと触れたはずです。はい。連想配列です。あの十干十二支の読み方まで出力したあれです。実は、連想配列のキーと値の値の箇所は固定値だけではなく関数をそのまま投げ入れることもできます。型の定義が緩いとはそういう事であり、配列もオブジェクトだし、とかぶつぶつ言っていたのはこういうことができる事を意味していました。

ですが、まだ疑問が残ります。どうやってinitializeをいきなり呼び出したのでしょうか。以前使った連想配列では、キーを名指ししないと値を取り出せなかったはずです。

というわけで、ここでまたグーグル先生にお問い合わせします。

クラス、という箇所にinitializeという単語がでてきます。引用します。

enchant.jsのクラスは、Class.createという関数で作ることが出来ます。

Bear = Class.create(Sprite, { //自作クラスBearの定義

initialize:function(){ //クラスの初期化(コンストラクタ)

Sprite.call(this,32,32); //スプライトの初期化

this.image = game.assets['fig1.png']; //画像の指定

game.rootScene.addChild(this);

}

});

こうしてクラスを定義すると、今度はもっと簡単にキャラクターを出せるようになります。

bear = new Bear(); これだけです。

なるほど、これだけですか。enchant.jsの機能でinitializeをこう書いたらnewと同時にinitializeのキーに対応するfunctionを自動的に実行してくれるわけですね。ということは、initialize以外の連想配列は指定することで実行する可能性が高いですね。特に、英語っぽくないものは。

もう一回、Playerのクラスを見てみましょう。new した時にいろいろ設定できるようになっています。ただ、ちょっと設定されすぎですねコレ。たとえばx座標の位置まで設定されています(したのは右側初期位置にしたかったからですが)。また、画像そのものを変えるわけではありませんので、assetsの設定変更も不要です。必要なのは、同じ画像で違う箇所に切り替える・・・frameの値だけかえればいいですよね。

ということで、都合のいい新しいfunctionを追加しましょう。連想配列を追加するのと同じ要領でいけるはず。

// make new class for player
var Player = enchant.Class.create(enchant.Sprite, {
    initialize: function(){
        enchant.Sprite.call(this, 32, 32);
        this.image = game.assets['chara7.png'];
        this.x = 288;// add:右側に表示。320-32の位置
        this.frame = 10;                   // set image data
        game.rootScene.addChild(this);     // add to canvas
    }, // ここのカンマは忘れないように。

    // touchstartイベントで利用する
    attackstart: function(){
      this.frame = 16; // 斬っている瞬間の画像位置
    }
});

プレイキャラクターの表示定義を作ったので次は定義を呼び出す方法を考えます。

いままで攻撃したタイミングではリンゴを生成していました。この部分を、いま作ったattackstartを呼び出すようにすればいいはず。ということで。

// add event listener (called when click/touch started)
game.rootScene.on('touchstart', function(evt){
    player.y = evt.localY;    // set position to touch-y position
//        var apple = new Apple();
    player.attackstart(); // 追加
});

こんな感じ。さて動かしてみましょう、きっと完璧だッ!!!!(フラグ)

画像

・・・・・。

なんか ちょっと ちがう。

そうですね。いつまでも剣を前に出しておくわけがありませんよね。こう、シャッ!!とならないとかっこよくありませんよね。

シャッ!!してみる

シャッ!!とは具体的に何をしているか。スローモーションで想像してみます。

  1. クリックすると同時にシャッ!!のシがスタート
    • →touchstartのなかを変更した
  2. コンマ数秒程度の間、シャッ!!のャッのあたり
    • →シャッ!!をやってはいる(コンマ数秒どころかずっとやってるけど)
  3. コンマ数秒がすぎるとシャッ!!の!!が自動的に発動し、元の直立姿勢に戻る
    • →コンマ数秒をどうやって測るのか。

最後のやり方ですよね。たとえばクリックやキータッチの操作の時に変化させる、というのは今までもやってきました。ですが、ボタンアクション以外の変化をどうやって察知しているのでしょうか。

いつもながらにた事例を考えてみます。たとえばリンゴが「当たった時」はどうでしょうか。

当たったというのはおおまかに言えばx座標とy座標が一致しているかどうかを判定すれば良さそうな気がします。ですが、いつ判定しているのでしょうか。

なんとなくどの辺りを見ればいいかわかってきました。このあたりっぽいですね。

  game.rootScene.on('enterframe', function(){
    var hits = Apple.intersect(Enemy);
    for(var i = 0, len = hits.length; i < len; i++){
        game.rootScene.removeChild(hits[i][0]);
        game.rootScene.removeChild(hits[i][1]);
        score ++;
    }
  });

ここで「いつ」という判定はしているみたいですが、「何をするか」(リンゴがあたっているか判定して処理→シャッ!!を戻す)が変わります。とりあえずコメントアウトしちゃいましょうか。

game.rootScene.on('enterframe', function(){
/*
  var hits = Apple.intersect(Enemy);
  for(var i = 0, len = hits.length; i < len; i++){
      game.rootScene.removeChild(hits[i][0]);
      game.rootScene.removeChild(hits[i][1]);
      score ++;
  }
*/
});

enterframeの時の処理がまるまるなくなっちゃいました。今度はこの代わりに「元の直立姿勢に戻る処理を呼び出す処理」を書けばいいのですが、その前に。

直立姿勢に戻る処理は書いてないのでこれも追加しましょう。Playerの定義に追加すればいいでしょう。

var Player = enchant.Class.create(enchant.Sprite, {
  //(中略)
  attackstart: function(){
    this.frame = 16; // 斬っている時の画像位置
  },
  attackend: function(){
      this.frame = 10; // 普段の画像位置
  }
});

呼び出し先も書いたことですし、気を取り直して「元の直立姿勢に戻る処理を呼び出す処理」を書きます。

game.rootScene.on('enterframe', function(){
  player.attackend(); // 攻撃終了時
/*
  var hits = Apple.intersect(Enemy);
  for(var i = 0, len = hits.length; i < len; i++){
      game.rootScene.removeChild(hits[i][0]);
      game.rootScene.removeChild(hits[i][1]);
      score ++;
  }
*/
});

ぬぬぬ。思ってる動きと違う。ですが、焦ってはいけません。

状態管理をする

実は、まだソースコードに書いていない機能があります。「コンマ数秒程度の『間』、シャッ!!のャッのあたり」です。

ここで少し、player.attackend();を含んでいるgame.rootScene.on('enterframe', function(){});について雑に調べてみましょう。このあたりを斜め読みするとなんとなくわかってくるかもしれません。

雰囲気的には、フレームと呼ばれるパラパラ漫画の1枚をパラするたびに呼び出される機能のようです。というわけで、「数秒程度の『間』」というのは、この機能を何回呼び出したかをカウントすることで何とかなりそうです。

もう少し掘り下げて、何ができればいいかを洗い出してみましょう。

  1. 'enterframe' が呼び出される
  2. playerの画像が攻撃状態になっているか確認する
  3. 攻撃状態の場合
    1. プレイキャラクターが「攻撃状態である事を何回確認したか」の数値を設定する
    2. 3回確認した後の場合、攻撃状態をやめ、元の直立画像に戻す

このうち「何回確認したか」を保持しておくものは今は無いので適当に作ります。

要は新しい変数やらプロパティやらを設定するという事なのですが、これをどこに作るかは好きなところでいいと思います。たとえば var score = 0;のすぐ下にvar attacking = 0; のようなものでもいいと思いますし、Playerオブジェクトの中に作るのでもいいと思います。

前者の場合は「画面の情報としてどの程度の時間プレイキャラクターが攻撃状態か」を管理する事になります。後者なら「プレイキャラクター自身の情報として、どの程度の時間自分が攻撃状態か」を管理する事になります。どちらも結果的には同じ3フレーム後に直立状態に戻りますので、好みの方でいいと思います。

画面の情報として状態管理する場合)

// make new class for player
var Player = enchant.Class.create(enchant.Sprite, {
    initialize: function(){
        enchant.Sprite.call(this, 32, 32);
        this.image = game.assets['chara7.png'];
        this.x = 288;// add:右側に表示。320-32の位置
        this.frame = 10;                   // set image data
        game.rootScene.addChild(this);     // add to canvas
    },
    attackstart: function(){ // 攻撃表示にする用
        this.frame = 16; // 斬っている瞬間の画像
    },
    attackend: function(){ // 攻撃表示を元に戻す用
        this.frame = 10; // 普段の画像
    }
});

//
// (ここからしばらく省略)
//

var score = 0;
var attacking = 0; // 確認回数

game.rootScene.on('enterframe', function(){
    if( player.frame == 16) { // プレイキャラクターが攻撃状態画像の場合
        attacking ++; // 確認した回数
    }
    if( attacking == 3 ) { // 攻撃状態を3回確認した時
        player.attackend(); // 攻撃表示を元に戻す
        attacking = 0; // 確認回数を元の値に戻す
    }
/*
    (略)
*/
});

プレイキャラクター自身の状態として管理)

// make new class for player
var Player = enchant.Class.create(enchant.Sprite, {
    initialize: function(){
        enchant.Sprite.call(this, 32, 32);
        this.image = game.assets['chara7.png'];
        this.x = 288;// add:右側に表示。320-32の位置
        this.frame = 10;                   // set image data
        this.attackframe = -1; // 攻撃状態を何フレーム持ったかの確認用。0未満が非攻撃状態
        game.rootScene.addChild(this);     // add to canvas
    },
    attackstart: function(){
        this.frame = 16; // 斬っている瞬間の画像
        this.attackframe = 0; // 攻撃状態確認0回目
    },
    attackend: function(){
        if( this.attackframe < 0 ) return; // 攻撃状態では無い場合は後続処理をしない
        if( this.attackframe == 3 ) {
            this.frame = 10; // 普段の画像
            this.attackframe = -1; // 非攻撃状態に設定
        } else {
            this.attackframe ++; // 確認回数インクリメント
        }
    }
});

//
// (しばらく省略)
//

game.rootScene.on('enterframe', function(){
    player.attackend();
/*
    (略)
*/
});

どちらのほうが良いというのもありませんし、どちらの書き方を取ったとしてもこの通り書かないといけないというわけでもありません。まずは自分がわかりやすい書き方をやってみれば良いです。

私より上手く綺麗にプログラミングする人はたくさんいます。良い書き方はそういう方のソースコードを参考にして経験を重ねるのが良いでしょう。