デザインでブランディングのお手伝い
デザインでブランディングのお手伝い
2019.01.22
#177
それいけ!フロントエンド

「phina.js」を使って毛を抜くアクションを作ってみよう【いろんな動きを作ってみようシリーズ】

はっちゃん

こんにちは、フロントエンドエンジニアのはっちゃんです。最近は抜け毛を気にすることもなくなりました。

突然ですが今回は、canvas用ライブラリを使って「canvasに頭皮と一本の毛をおいて、ドラッグして引っ張って抜く」アクションを作ってみようと思います。

準備するもの

ゲーム用ライブラリ「phina.js」

-アイディアを即座に形にできるゲームライブラリです
-初心者でも手軽にゲームを開発できます
-様々な Web コンテンツ, アプリで多数の採用実績があります
-大学や専門学校といった教育機関で利用されています
-国産かつオープンソースでTwitter 駆動開発なので気軽にコントリビューターになれます

引用元:http://phinajs.com/

以前シスコくんが使い方を書いてくれているので、こちらも読んでおくといいでしょう。

1本の毛

1本の毛

イラレでサクッと作りました。こいつを引っこ抜いていきます。

HTML、CSS、JSコーディング

ではコーディングしていきましょう。

HTML

<div id="example"></div>
<div id="js-modal-bg" class="modal-bg dn">
  <div class="modal">
    <img src="https://82mou.github.io/src/images/moukon.png" alt="">
  <p class="modal__text">毛が抜けました</p>
    <span id="js-btn" class="btn">もう一回抜く</span>
  </div>
</div>

CSS

.modal-bg {
  position: fixed;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1;
}
.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 400px;
  padding: 20px;
  background: rgba(255, 255, 255, 1);
  border-radius: 6px;
  z-index: 1;
  text-align: center;
  img {
    width: 40px;
  }
}
.modal__text {
  margin-top: 20px;
}
.btn {
    display: inline-block;
    margin-top: 10px;
    padding: 0.3em 1em;
    text-decoration: none;
    color: #67c5ff;
    border: solid 2px #67c5ff;
    border-radius: 3px;
    transition: .4s;
}
.btn:hover {
    background: #67c5ff;
    color: white;
}
.dn {
  display: none;
}

JS

それではJSで機能を実装していきます。

作る前に、必要なイベント、関数を洗い出しておきましょう。

  • phina.jsを使用する準備
  • 1本の毛画像の読み込み
  • 毛画を抜いた時の効果音の読み込み
  • シーンの作成
    • 頭皮レイヤー作成
    • 毛レイヤーを作成
      • マスクを作成
    • タッチした時のイベントを作成
    • タッチ中のイベントを作成
      • 一定数動かした時にモーダルを表示させる
    • タッチ後のイベントを作成
    • codepenのリセット
  • レンダリング

あとはこれにそってコードをガンガン描いていくだけです!

// グローバルに展開
phina.globalize();
// アセット
var ASSETS = {
  // 画像
  image: {
    'moukon': 'https://82mou.github.io/src/images/moukon.png',
  },
  sound: {
    'sound': 'https://82mou.github.io/src/sound/cork-plug1.mp3',
  },
};

/*
 * メインシーン
 */
phina.define("MainScene", {
  // 継承
  superClass: 'DisplayScene',
  // コンストラクタ
  init: function(option) {
    _this = this; // thisを保存
    this.pullFlg = false; // 抜いたかどうかのフラグ
    
    // 親クラス初期化
    this.superInit(option);
    
    // CircleShapeを作成
    let circle = CircleShape({
      radius: 1000,
      fill: "#FFD8BD",
      stroke: 0
    }).addChildTo(this).setPosition(this.gridX.center(),this.gridY.center(15));
   
    // スプライト画像を作成
    this.sprite = Sprite('moukon').addChildTo(this);
    
    // スプライト画像の初期位置設定
    this.sprite.x = this.gridX.center();
    this.sprite.y = this.gridY.center(-1.5);
    this.sprite.width = 80;
    this.sprite.height = 204;
    this.sprite.backgroundColor = 'red';
    
    // スプライト画像の初期設定を保存
    this.firstInitialPositionX = this.sprite.x;
    this.firstInitialPositionY = this.sprite.y;
    this.firstHeight = this.sprite.height;
    
    // スプライト画像の原点を左上に設定
    this.sprite.origin.set(0.5, 0);
    
    // マスクを作成
    let mask = RectangleShape({
        fill: '#FFD8BD',
        stroke: 0,
        width: 120,
        height: 300
    }).addChildTo(this).setPosition(this.gridX.center(),this.gridY.center(2));
    
    // スプライト画像のドラッグ可能にする
    this.sprite.draggable;

    // ドラッグ開始時
    this.sprite.ondragstart = () => {
      
      // ドラッグで中心より左右にずれたら中心に戻す
      this.sprite.x = this.gridX.center();
      
      // ドラッグで中心より下にずれたら中心に戻す
      if(this.sprite.y > this.gridY.center()) {
        this.sprite.y = this.gridY.center();
      }
    };
    // ドラッグ中
    this.sprite.ondrag = () => {
      
      // ドラッグで中心より左右にずれたら中心に戻す
      this.sprite.x = this.gridX.center();
      
      // ドラッグで中心より下にずれたら中心に戻す
      if(this.sprite.y > this.gridY.center()) {
        this.sprite.y = this.gridY.center();
      }
      
      // spriteの移動量を保存
      this.afterPositionX = this.firstInitialPositionX - this.sprite.x;
      this.afterPositionY = this.firstInitialPositionY - this.sprite.y;
      
      // spriteの移動量 + 補正値でドラッグ後の高さを保存
      this.afterHeight = this.firstHeight + (this.afterPositionY + 100);
      
      // spriteの移動後の高さが元の高さより小くならないように設定
      if(this.afterHeight < this.firstHeight) {
        this.sprite.height = this.firstHeight;
      } else {
        this.sprite.height = this.afterHeight;
      }
      
      // 一定数毛を上にドラッグしたら発火
      if(!this.pullFlg && this.sprite.y < 150) {
        this.pullFlg = true;
        
        // spriteのheightを1.5秒かけて元に戻す
        this.sprite.tweener.to({height: _this.firstHeight}, 1500, 'easeOutElastic') 
        
        // 音再生
        SoundManager.play('sound');
        setTimeout(() => {
          document.querySelector('#js-modal-bg').classList.remove('dn');
        },700);
      }
    };
    // ドラッグ終了時
    this.sprite.ondragend = () => {
      
      // ドラッグで中心より左右にずれたら中心に戻す
      this.sprite.x = this.gridX.center();
      
      // ドラッグで中心より下にずれたら中心に戻す
      if(this.sprite.y > this.gridY.center()) {
         this.sprite.y = this.gridY.center();
      }
    };
    
    // codepenをリセット
    document.querySelector('#js-btn').addEventListener('click', () => {
      history.go(0);
    });
  }
});

/*
 * メイン処理
 */
phina.main(function() {
  // アプリケーションを生成
  var app = GameApp({
    // MainScene から開始
    startLabel: 'main',
    width: 2000,
    height: 1000,
    // アセット読み込み
    assets: ASSETS,
  })
  // 実行
  app.run();
});

完成

See the Pen
pull out animation
by k_hatsushi (@hatsushi_kazuya)
on CodePen.

操作方法
毛を引っ張ってみよう!

まとめ

いかがでしたか? 引っ張るときや抜けるときのアニメーションをこだわるとよりリアルになりそうですね! みなさんもよろしければ試してみてください! はっちゃんでした。

LIGにWeb制作について相談してみる