p5.play Spriteのクローンで遊んでみた

# p5.play Spriteのクローンで遊んでみた

別記事 [p5.play Spriteのクローン](https://qiita.com/harhid/items/229f5bb8406e7389feee) にて、p5Playのスプライトをクローンしてみました。

今回は少しだけ方法を変えて、クローンならではの遊びをしてみました。
作った作品のタイトルは『クローンを噴射してロケット風に移動していく』です（名付けセンス微妙だ）。

(2023/10/25 追記)
WebEditor にソースがあるので公開しておきます
https://editor.p5js.org/amami-harhid/full/GXpYaQUh1

## 変えるところ

前回のクローンのやり方は『完全なクローン（すべてに対応）』を目指した実装にトライし、途中で無理だとあきらめたために、実装が多少わかりにくくなっていました（意味がないコードといえばいいのかな？）。

今回は、クローンに引き継ぐ事柄を絞りこみ、他は引き継がずに捨ててしまうシンプルな実装を目指します。
引き継ぐものを絞ることで、多少見やすい（わかりやすい）コードになればいいなと思います。

どうせ『完全なクローンの方法』はできない（と思う）ので、作りたい作品に応じてクローンの方法(実装)を変えていったらよいと思います。

- クローンインスタンスを作るところでコンストラクター引数を与える
- オリジナルのプロパティすべてをクローンへコピーしない。（必要なものだけ狙い撃ちでコピーする）

## 引き継ぐ事柄

- 現在の座標（x,y)
- animation
- collider
- scale

これだけクローンへ引き継げたらよいとします。

## 追加で取り組みたい事柄

- Sprite継承した新クラス内で独自の動作を定義する( update 処理 )

# 今回、実現したい動き

- スプライト『星』が現在地にクローンを作り続け、クローンの噴射によりロケット風に移動していく
- クローンは LIFE を与え、LIFEを消耗すると削除される( フレーム(1)ごとに Life(1)を消耗する )
- Colliderは元のスプライトに設定した同じ形を再現させる。
- 障害物を用意して『星』とそのクローンが衝突したら跳ね返るようにします。

## 動作の例

![p5play_pico36.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294946/2eb50fcf-fde2-fa51-fb73-f81a92ae6bd9.gif)


## js/Pico.js
```: js/Pico.js
p5.prototype.registerMethod('init', function() {

  let _p5 = this;
  
  _p5.PicoSprite = class extends _p5.Sprite {
      
    constructor(...args) {
        
      super(...args);
      // 独自のupdate処理を設定する
      // draw処理のタイミングで update()が実行される
      this.update = this._updateCustomize;
    }
    
    /*
     *  Update処理を変更する   
     */
    _updateCustomize() {
      
      if( this.thisIsClone ){
        // クローンのときは、外枠から出たら消える。
        if(this.x > 0 && this.x < W  &&
            this.y > 0 && this.y < H ) {
        } else {
          this.remove();
        }
      } else {
        // 本体のときは、外枠から出たら反対側から出現。
        if(this.x > W) {
          this.x = 0;
        } else if( this.x < 0) {
          this.x = W;          
        }
        if(this.y > H) {
          this.y = 0;
        } else if( this.y < 0) {
          this.y = H;          
        }
      }
    }
    

```
```: js/Pico.js（続き)
    /*
     *  自分をクローンする
     *  ・クローンを作るときには元のスプライトの情報を引数に与える。
     *  ・アニメーションは元スプライトからクローンして作る
     *  ・常にオリジナルスプライトを上位レイヤーにする。
     *  ・Colliderの設定は内部で記憶しておき、設定を再現する。
     *  ・クローンの場合はクローンの印をプロパティとして用意する。
     */
    clone() {
      let _src = this; // thisを退避する
      let _clone = new _src.constructor(
                    _src.x,_src.y,
                    _src.w,_src.h, _src.collider);
      // アニメーションを引き継ぐ
      _clone.ani = _src.ani.clone();
      // 元のスプライトを上にする(Layerを交換する)
      let srcLayer = _src.layer;
      let cloneLayer = _clone.layer;
      _clone.layer = srcLayer;
      _src.layer = cloneLayer;
      // Colliderを再現させる
      _clone._reproduceCollider(_src);
      // クローンの印をつける
      _clone.thisIsClone = true;
      return _clone;
    }
    
```
```: js/Pico.js（続き)
    /*
     *  removeColliders()のオーバーライド   
     */
    removeColliders() {
      super.removeColliders();
      this._saveColliderArg("remove", []);  
    }
    
    /*
     *  addCollider()のオーバーライド   
     */
    addCollider(...args) {
      super.addCollider(...args);
      this._saveColliderArg("add", args);
    }
    
    /*
     *  Colliderを再現する   
     */
    _reproduceCollider(_src) {
      let _args = _src._getColliderArgs();
      _args.forEach( (_m) => {
        if( _m.has("remove")) {
          this.removeColliders();
        } else if(_m.has("add")) {
          let _args = _m.get("add");
          this.addCollider(..._args)
        }
      });
      // Colliderを再現した後にScaleを再設定することで
      // Colliderの大きさを整える
      this.scale = _src.scale;
    }
    
    _getColliderArgs() {
      if( this._colliderArgArr === undefined ) {
        this._colliderArgArr = [];
      }
      return this._colliderArgArr;
    }

    _saveColliderArg(_k, _v=[]) {
      let _args = this._getColliderArgs();
      const _m = new Map();
      _m.set(_k,_v);
      _args.push(_m);
    }
  };
});
```

## sketch.js

```:sketch.js
window.onload = () => {
  new p5( sketch );
};

const W = innerWidth * 0.5;
const H = innerHeight * 0.5;

const sketch = function( p ) {

　p.preload = function(){
    starImage = p.loadImage("assets/star.svg");
  };
  
  p.setup = function(){
    p.world.gravity.y = 2;
    canvas = new p.Canvas(W,H);
    canvas.position(W/2,H/2);
    pico = new p.PicoSprite();
    pico.addAni(starImage);
    pico.x = W/2;
    pico.y = H/10;
    pico.removeColliders();
    pico.addCollider(0,-10, 30)
    pico.addCollider(0, 10, 30)
    pico.addCollider(10, 10, 30, 30)

    // Collider設定の後にScaleを設定することで
    // Colliderのサイズも一緒に整えることができる
    pico.scale = 0.8;
    // 障害物を用意（３個）
    rect01 = new p.Sprite(W/2, H/2, 10, 25, "s");
    rect02 = new p.Sprite(W/2+100, H/2-100, 25, 25, "static");
    rect03 = new p.Sprite(W/2-100, H/2+50, 25, 25, "static");

    p.background(0);
    p.noLoop();
  };

  p.draw = function(){
    p.background(0, 50);
    let _c = pico.clone();
    _c.scale = 0.3; // クローンは小さめにする
    _c.life = p.random(5,400); // クローンのLIFEはランダムな値
    // 障害物を回転させる
    rect01.rotation  += 10;
    rect02.rotation  -= 2;
    rect03.rotation  += 5;
  };
  
  p.mousePressed = function() {
    p.loop(); // マウスクリックしたら動作開始する。
  }

};
```