/* global phina:false */
const SCROLL_TYPES = ['instant', 'linear', 'slip'];
const DEFAULT_PARAMS = {
lockX: false,
lockY: false,
scrollType: 'linear',
scrollSpeed: 8,
scrollFriction: 0.1,
autoUpdate: true,
}
/**
* @typedef {Object} Vector2
* @property {number} x
* @property {number} y
*/
/**
* 対象に合わせて自身の位置を調整し、スクロールしているように見せるレイヤークラス。
* @class phina.display.ScrollLayer
* @memberOf phina.display
* @extends phina.display.DisplayElement
*
* @example
* phina.globlize();
* const SCREEN_WIDTH = 720;
* const SCREEN_HEIGHT = 1080;
*
* // in Scene Class...
*
* // setup layer
* const layer = ScrollLayer({
* lockY: true,
* })
* .setCoordinate(SCREEN_WIDTH/2, SCREEN_HEIGHT/2)
* .addChildTo(this);
*
* // setup target
* this.player = Sprite('player').addChildTo(layer);
* layer.setTarget(this.player)
*
* @param {object} [options] - phina.display.DisplayElementのパラメータも有効です。
* @param {string} [options.scrollType=linear] - スクロールの方法を指定:'instant', 'linear', 'slip'
* @param {number} [options.scrollSpeed=8] - スクロール速度を指定。scrollType:'linear'の時のみ有効
* @param {number} [options.scrollFriction=0.1] - slipスクロール時のフォーカス挙動を指定。scrollType:'slip'の時のみ有効
* @param {boolean} [options.lockX=false] - x軸スクロールを禁止
* @param {boolean} [options.lockY=false] - y軸スクロールを禁止
* @param {boolean} [options.autoUpdate=true] - スクロールを自動で行うかどうか。falseの場合、都度updatePositionを呼び出す必要があります
*/
export default phina.createClass({
superClass: phina.display.DisplayElement,
init: function(options) {
options = ({}).$safe(options, DEFAULT_PARAMS);
this.superInit(options);
this._isLockedX = options.lockX;
this._isLockedY = options.lockY;
this._scrollType;
this._focusTarget = options.focusTarget || phina.geom.Vector2(0, 0);
this._coordinate = phina.geom.Vector2(0, 0);
if (options.coordinate)
this.setCoordinate(options.coordinate.x, options.coordinate.y);
this.scrollSpeed = options.scrollSpeed;
this.scrollFriction = options.scrollFriction;
this.scrollType = options.scrollType;
if (options.autoUpdate)
this.on('enterframe', this.updatePosition.bind(this));
},
/**
* @deprecated since version 0.x
* @return {void}
*/
_updatePosition: function() {
console.warn("[phina-extensions]: deprecated. updatePosition is no longer private")
return this.updatePosition();
},
/**
* focusTargetがcoordinate座標に来るようレイヤーそのものをずらす。
* 位置が変わらないときは何もしない<br>
* Move layer position to focus the specified target
* @instance
* @private
* @memberof phina.display.ScrollLayer
*
* @return {void}
*/
updatePosition: function() {
const destX = this._coordinate.x - this._focusTarget.x;
const destY = this._coordinate.y - this._focusTarget.y;
const deltaX = destX - this.x;
const deltaY = destY - this.y;
// x-axis update
if (!this._isLockedX && deltaX !== 0) {
switch (this._scrollType) {
case "instant":
// 瞬時に合わせる
this.x = destX;
break;
case "linear":
// 線形移動で合わせる:ちょっとずつ定数加算
this.x = (destX > 0)
? Math.min(destX, this.x + this.scrollSpeed)
: Math.max(destX, this.x - this.scrollSpeed)
break;
case "slip":
// 滑るように合わせる:差分(目標値 - 現在値) * 0.1 を ちょっとずつ加算することでなめらかに
this.x += (destX - this.x) * this.scrollFriction;
break;
// TODO: custom scrolling feature
// case "custom":
// this.customXScroll();
// break;
default:
// same as instant
this.x = destX;
break;
}
}
// y-axis update
if (!this._isLockedY && deltaY !== 0) {
switch (this._scrollType) {
case "instant":
this.y = destY;
break;
case "linear":
this.y = (destY > 0)
? Math.min(destY, this.y + this.scrollSpeed)
: Math.max(destY, this.y - this.scrollSpeed)
break;
case "slip":
this.y += (destY - this.y) * 0.1;
break;
// TODO: custom scrolling feature
// case "custom":
// this.customYScroll();
// break;
default:
this.y = destY;
break;
}
}
},
/**
* 注視対象を指定<br>
* Set focusTarget
* @instance
* @memberof phina.display.ScrollLayer
*
* @param {Vector2} focusTarget
* @return {this}
*/
setTarget: function(focusTarget) {
this._focusTarget = focusTarget;
return this;
},
/**
* 注視対象の画面表示位置をセット<br>
* 例えば画面中心に映したいなら画面サイズ半分を指定する<br>
* Set coordinate of focusTarget screen position.
* layer.setCoordinate(this.width/2, this.height/2)
* @instance
* @memberof phina.display.ScrollLayer
*
* @param {number} x - X coordinate
* @param {number} y - Y coordinate
* @return {this}
*/
setCoordinate: function(x, y) {
this._coordinate.set(x, y);
return this;
},
/**
* Lock x or y-axis scrolling
* @instance
* @memberof phina.display.ScrollLayer
*
* @param {boolean} x - lock x-axis scroll
* @param {boolean} y - lock y-axis scroll
* @return {this}
*/
setLock: function(x, y) {
this.lockX = x;
if (y !== undefined) this.lockY = y
return this;
},
_accessor: {
/**
* @property scrollType
*/
scrollType: {
get: function() { return this._scrollType; },
set: function(v) {
v = v.toLowerCase();
if (!SCROLL_TYPES.contains(v)) {
console.warn("[phina warn] scroll type '{0}' does not exist.".format(v))
}
this._scrollType = v;
}
},
/**
* @property coordinate
* getter only
*/
coordinate: {
get: function() { return this._coordinate.clone(); },
},
/**
* @property lock
* setter only
*/
lock: {
set: function(v) {
this._isLockedX = (v === true);
this._isLockedY = (v === true);
}
},
/**
* @property lockX
*/
lockX: {
get: function() { return this._isLockedX; },
set: function(v) {
this._isLockedX = (v === true);
}
},
/**
* @property lockY
*/
lockY: {
get: function() { return this._isLockedY; },
set: function(v) {
this._isLockedY = (v === true);
}
},
},
_static: {
defaults: DEFAULT_PARAMS,
},
});