Posted at
phina.jsDay 16

[phina.js]Colliderアクセサリを作ってみた話

More than 1 year has passed since last update.


きっかけ

phina.jsでは、当たり判定メソッドがあらかじめ用意されていて便利ですが、少し凝った当たり判定をしたい時は当たり判定用のダミーオブジェクトを使うなど、自分で工夫する必要があります。

例えばUnityでは、オブジェクトにColliderというコンポーネントを追加すると、視覚的に衝突の判定が確認できます。このColliderのようなものがphina.jsで実装できないかと思ったのがきっかけです。


ColliderをAccessoryとして設計

phina.jsにはUnityのコンポーネント方式のようなAccessoryというクラスがあります。その名前のとおり、アクセサリーのように様々な機能などの着脱ができるようにしたものです。phina.jsのサンプルに良く見られるTweenerPhysicalAccessoryを継承しています。

作り方などは、phina.jsでアクセサリを使ってみよう&作ってみよう!が参考になると思います。


サンプル

collider.gif

[runstantで確認]


  • サンプルでは、2つのキャラクターが壁に向かって移動し、壁にぶつかると反転移動します。

  • キャラの周りに表示されているのが今回作ったColliderです。


  • Colliderを単純にアタッチすると、下のキャラのようにスプライトのサイズに合わせた大きさになります。

  • 上のキャラは、Colliderを実際の画像ギリギリの幅にサイズ調整しているので、衝突の仕方に違いが出ているのが分かるかと思います。


使い方


ヘッダーで読み込む

私個人のリポジトリにアップしていますので、htmlで以下のように読み込めば使用できます。

<script src="http://cdn.rawgit.com/phi-jp/phina.js/v0.2.1/build/phina.js"></script>

<script src="https://rawgit.com/alkn203/phina-accessory/master/collider.js"></script>


対象オブジェクトにアタッチする

obj.collider.show();


  • 一番簡単な方法は、上のようにオブジェクトのプロパティとしてアクセスしながらアタッチする方法です。

  • デフォルトでは非表示になっているので、showで表示させます。


サイズを変える

obj.collider.setSize(width, height);


  • デフォルトでは対象オブジェクトのサイズになりますが、サイズを指定して変更することができます。

  • 意図的に当たり判定を小さくしたい時などに使えます。


位置を変える

obj.collider.offset(x, y);


  • デフォルトでは対象オブジェクトの中心に表示されますが、オフセット値(相対座標)を指定して位置を変更することができます。

  • 対象オブジェクトより少し前にずらして、先読み判定でめり込みを防止する時などに使えます。


当たり判定を行う

obj.collider.hitTest(target.collider);


  • このメソッドの引数は、同じColliderです。つまり、対象オブジェクトにもColliderがアタッチされている必要があります。

  • ヒットすればtrue、そうでない場合はfalseを返します。


課題


  • パフォーマンスは二の次に考えて設計しましたので、大量のオブジェクトの場合どうなるかは未検証です。

  • 今の仕様では1つのColliderで矩形のみですので、Unityのように複数のColliderかつ、矩形だけでなく円もということになると、typeやidで区別するなど抜本的な仕様変更が必要かと思います。


まとめ

今回はColliderをサンプルにしましたが、phina.jsAccessoryの拡張性を少しは感じて頂けたかと思います。皆さんも試しにアクセサリーを1つ作ってみてはいかがでしょうか。結構楽しいですよ。


Colliderアクセサリのソース

/*

* collider
*/

phina.namespace(function() {
/**
* @class phina.accessory.Collider
* Collider
* @extends phina.accessory.Accessory
*/

phina.define('phina.accessory.Collider', {
superClass: 'phina.accessory.Accessory',
/**
* @constructor
*/

init: function(target) {
this.superInit(target);
},
// アタッチされた時
onattached: function() {
if (!this._collider) {
this._collider = RectangleShape({
width: this.target.width,
height: this.target.height,
fill: null,
}).addChildTo(this.target);

this._collider.hide();
}
},

ondetached: function() {
if (this._collider) {
this._collider.remove();
}
},
// 表示
show: function() {
this._collider.show();
return this;
},
// 非表示
hide: function() {
this._collider.hide();
return this;
},
// 相対位置指定
offset: function(x, y) {
this._collider.setPosition(x, y);
return this;
},
// サイズ指定
setSize: function(width, height) {
this._collider.setSize(width, height);
return this;
},
// 衝突判定
hitTest: function(collider) {
if (!this.target) return;
// 絶対座標の矩形を計算
var rect = this.getAbsoluteRect();
var rect2 = collider.getAbsoluteRect();
// 矩形同士で判定
return phina.geom.Collision.testRectRect(rect, rect2);
},
// Colliderの絶対座標の矩形
getAbsoluteRect: function() {
var x = this._collider.left + this.target.x;
var y = this._collider.top + this.target.y;
return phina.geom.Rect(x, y, this._collider.width, this._collider.height);
},
});

phina.app.Element.prototype.getter('collider', function() {
if (!this._collider) {
this._collider = phina.accessory.Collider().attachTo(this);
}
return this._collider;
});
});