TweenXCore
TweenXCoreは Haxe で気持ちの良いモーションをより簡単に作成するための、クロスプラットフォームなトゥイーンライブラリです。
実行速度が速く、開発速度が速く、バグが少なく、学習コストが少なく、そしてモーションが自由で面白くなるように作られています。
機能
-
44種類のイージング関数
-
繰り返し
-
ヨーヨー、ジグザグ運動
-
イージングのミックス、クロスフェード、連結などのカスタム機能
-
HSVカラー、RGBカラー
-
極座標
-
ベジェ曲線
-
lerp、inverseLerp、clampなど、Floatの機能の拡張 -
イージング作成ツールとの連携
速度
余計な処理やインスタンス生成を行わないため、非常に高速に動作します。
以下は、Flashプラットフォームでの各トゥイーンライブラリとの比較です。250,000個のオブジェクトを同時にトゥイーンさせた場合のFPSを測定しました。
ライセンス
MITライセンスですので、商用、非商用、個人、法人を問わず、利用、改変、再配布ができます。
TweenXCoreを始める
Haxeバージョン
Haxeの3.1.3以降をサポートしています。
インストール
Haxeのインストール後、コマンドラインから以下のコマンドを入力してください。
haxelib install tweenxcore
Hello TweenXCore
TweenXCoreの最初のサンプルとして、四角のx座標を0から450まで、動かすコードを見てみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 import sample.Sprite;
import sample.Square;
using tweenxcore.Tools;
class SimplestSample extends Sprite {
public static var TOTAL_FRAME:Int = 20;
private var square:Square;
private var frameCount = 0;
public function new() {
super();
// 正方形を初期位置に配置
addChild(square = new Square());
square.y = Square.SIZE * 2.0;
}
// 毎フレーム、この関数を呼び出す
public function update():Void {
var rate = frameCount / TOTAL_FRAME;
// rateが0から1のとき、アニメーション。
if (rate <= 1) {
// rateの値に応じて、xを0から450まで動かす。
square.x = rate.quintOut().lerp(0, 450);
}
frameCount++;
}
}
TweenXCoreに関係するコードは、以下の部分です。
using tweenxcore.Tools;
square.x = rate.quintOut().lerp(0, 450);
それでは1つずつ要素を見ていきます。
using tweenxcore.Tools
ここで読み込んでいるtweenxcore.Toolsモジュールは4種類のクラスを持っています。
- Easingクラス
-
モーションの曲線として使われる、イージング関数を持ちます
- FloatToolsクラス
-
Floatの機能を拡張します。
- PointToolsクラス
-
XY座標上の点の拡張です。ベジェ曲線の機能を付け加えます。Flashの
Pointクラスに対してだけではなく、様々なライブラリのPoint型に対して使えます。 - MatrixToolsクラス
-
XY座標のアフィン変換の行列の拡張です。2次元的な動きを相似変換するための機能を付け加えます。Flashの
Matrixクラスに対してだけではなく、似たインターフェースを持つその他のライブラリのMatrix型に対して使えます。
using tweenxcore.Tools;でこのすべてを拡張として読みこみます。
チュートリアルで紹介する関数の多くはこのToolsのモジュールにありますから、そのソースを参照しながら読み進めると理解がしやすいかと思います。
イージング関数
quintOutはイージング関数です。0から1へと変化するrateの値を、5次関数をつかって後詰めの値へと変えています
TweenXCoreが提供するイージング関数はもちろんquintOutのみではありません。
Robert Pennerのイージング関数を基本とし、中央で減速して再度加速するOutInのモードと、瞬間的に移動を行うwarpが追加された計44個の関数を提供しています。
これらの関数の中身が具体的にどうなっているのか少し見てみます。
TweenXCoreでの、cubicIn関数は以下の通りです。
function cubicIn(t:Float):Float {
return t * t * t;
}
ただ単にFloatを受け取ってFloatを返す、シンプルな関数です。
この関数に先ほどのサンプルコードのイージングを差し替えてみます。たった1行、以下のように変更を加えます。
square.x = rate.cubicIn().lerp(0, 450);
動きが、もとのquintOutから変わっているのがわかると思います。
lerp
lerp は線形補間の関数で、tweenxcore.Tools.FloatToolsが持つ関数です。
さきほどのサンプルでは、0.0から1.0の指定した範囲の値を、0から450までの値に変換しています。これによりrateが0.0から1.0まで変化する間に、square.xは0から450へと移動します。
TweenXCoreの思想
0.0から始まり、1.0で終わる
TweenXCoreの世界では、始まりの値は0.0であり、終わりの値は1.0です。
つまり、
-
モーションの時刻の場合、開始時刻は0.0で表し終了時刻は1.0を使います。
-
アルファ値の場合、完全な透明は0.0で完全な不透明は1.0です。
-
円の1周の場合、0度は0.0であり360度は1.0です。
-
RGBカラーのRed値の場合、0.0が赤みが全くなく1.0が完全な赤です。
TweenXCoreでは、このような0.0から1.0を基準とする値についてrateという変数名をよく使います。
脱ブラックボックス
TweenXCoreは、出発地点や到達地点を指定して自動でオブジェクトを動かすような機能は持ちません。
トゥイーンライブラリが自動的にオブジェクトを動かしてくれる機能はただモーションを再生するだけなら簡単ですが、少し凝ったことをしようとすると途端に難しくなります。
例えば、以下のようなことです
-
一時停止ボタンが押されたときにゲーム内のアニメーションを一時停止した上で、さらにアニメーション付きでポップアップを表示させたい
-
インジケータがフェードインしている途中に通信が終わったら、すぐにフェードアウトに切り替えをしたい
-
あるモーションについて、マウスダウン中だけスロー再生にしたい
多くのトゥイーンライブラリでは、こういった内容の実現はできなかったり、複雑な仕様をおぼえなきゃいけなかったりします。
それに対して、TweenXCoreの解決策は簡単です。
-
モーションを止めたければ、止めたい場所の更新をやめれば止まります
-
フェードインとフェードアウトは、単純な
if文で切り替えることができます -
フレームカウント(
frameCount)の上昇を1づつから、0.5づつにすれば、モーションは0.5倍速再生になります
何も難しいことがありません。
TweenXCoreが提供するのは、以下の3つです。
-
0.0から1.0の範囲ではない値を、0.0から1.0の数値に変換する機能。 (
FloatTools.inverseLerp関数や、FloatChangeクラス) -
0.0から1.0の数値を、別の曲線を描く0.0から1.0の数値に変換する機能。(
Easingやカスタムイージングの機能) -
0.0から1.0の数値を、様々な値へと変換する機能。(
FloatTools.lerpや、Timelineクラス)
たったこれだけの機能があれば、自由に思いのままのモーションを作ることができます。その方法については、チュートリアルで解説していきます。
どこでも使える
TweenXCoreは、プラットフォームや、あわせて使うフレームワーク、プログラミングのパラダイムに左右されることなく利用可能です。
-
目指しているスタイルが、オブジェクト指向でも、手続き型プログラミングでも、関数型プログラミングでもよくマッチします。
-
使用するフレームワークが、OpenFLであっても、Reactであっても、Unityであっても同じように動作します。
-
クライアントサイドでも、サーバーサイドでも、コンパイル時でも動作します。
TweenXCoreチュートリアル
イージングを自作する
TweenXCoreには44種類のイージングありますがこれらを単に使うだけでは、ありふれたモーションになりがちです。TweenXCoreではイージングを組み合わせたり混ぜ合わせたりして自分だけのイージングを作り出すことができます。
多重のイージング(関数合成)
イージングを2重、3重に使うと、新しい動きを作ることができます。
square.x = rate.cubicIn().bounceOut().lerp(0, 450);
cubicInを使ってからbounceOutを使ってことで、加速していくバウンドのイージングを作っています。
Mix
mixEasing is intermediate easing between the two easings.
square.x = rate.mixEasing(Easing.expoOutIn, Easing.linear, 0.18).lerp(0, 450);
サンプルはゲームのカットイン演出にありそうな動きです。expoOutInにlinear関数を0.18ミックスすることで、OutInのイージングの真ん中での静止を無くしています。
クロスフェード
crossfadeEasingは、始まりと終わりで別のイージングに徐々に変わっていくようなイージングです。
square.x = rate.crossfadeEasing(
Easing.quintOut,
Easing.bounceOut,
Easing.sineInOut
).lerp(0, 450);
サンプルはquintOutとして始まって、徐々にEasing.bounceOutに変わっていくイージングです。変化の仕方の曲線としてEasing.sineInOutを使っていました。
ヨーヨー
ヨーヨーは0.0から1.0に行って、逆再生の動きで0.0に帰ってくるモーションです。
// ヨーヨー
square.x = rate.yoyo(Easing.quintOut).lerp(0, 450);
ジグザグ
ジグザグは0.0から1.0に行って、移動方向を反転させた動きで0.0に帰ってくるようなモーションです。
// ジグザグ
square.x = rate.zigzag(Easing.quintOut).lerp(0, 450);
コネクト
connectEasingは、2つのイージングをつなげて再生する機能です。
square.x = rate.connectEasing(Easing.backOut, Easing.linear, 0.9, 0.4).lerp(0, 450);
サンプルでは、最初の0.9の時間で0.4の位置までbackOutで移動した後、残りをlinearで移動しています。
ワンツー
oneTwoEasingは、別々のイージングで2回移動を行うイージングです。
square1.x = rate.oneTwoEasing(Easing.backIn, Easing.linear, 0.7).lerp(30, 420);
backInで1回目の移動を、linearで2回目の移動を行っています。
CustomEasingクラス
このようなイージングのカスタム機能を何度も使う場合、自作のイージングをまとめたCustomEasingクラスを作っておくと便利です。
using tweenxcore.Tools;
class CustomEasing {
public static inline function quintQuintInOut(rate:Float) {
return rate.quintInOut().quintInOut();
}
}
このようにCustomEasingクラスを定義しておけば、自分の作ったイージングをusing packageName.CustomEasing;して簡単に利用できるようになります。
値の変化をあつかう(FloatChange)
これまでのサンプルは現在の値のみを使うものでしたが、直前の値と現在の値の両方を使うことで、さまざまな動作を作ることができます。
TweenXCoreでは、直前の値と現在の値をあつかうFloatChangeというクラスを提供しています。
値を横切った瞬間を取得
FloatChangeを使用する例として、フレームカウントが特定の値を横切った瞬間の判定があります。
public function update():Void {
var floatChange = new FloatChange(frameCount, frameCount += 1);
// フレームカウントが30.0を横切った瞬間に、画面全体に四角を表示
if (floatChange.isCrossOver(30.0)) {
addChild(square = new Square());
square.width = 481;
square.height = 151;
}
}
new FloatChangeの第1引数は直前の値previous、第2引数は現在の値currentで、FloatChangeはこの2つの値をあつかうための便利関数を提供します。
isCrossOver関数は、このpreviousとcurrentが指定した値を横切った瞬間のみtrueになります。
この例の場合はprevious <= 30.0 && 30.0 < currentまたはcurrent <= 30.0 && 30.0 < previousの条件で判定されます。
FloatChangeは例えば時間ベースでモーションをさせる場合に役に立ちます。new FloatChange(previousTime, currentTime)としたときに、previousTimeとcurrentTimeがたまたま同一の値になったとしても、isCrossOverで判定した処理が2重に呼び出されることはありません。
値がある区間にいる間を取得
フレームが特定の区間にある時のモーションです。
public function update():Void {
var floatChange = new FloatChange(frameCount, frameCount += 1);
floatChange.handlePart(20.0, 50.5, updatePart);
}
private function updatePart(part:FloatChangePart):Void {
var left = part.previous.expoOutIn().lerp(0, 480);
var right = part.current.expoOutIn().lerp(0, 480);
square.x = left;
square.width = right - left;
}
handlePart関数は、FloatChangeが指定した区間を移動しているときに、すぐに(同期処理で)第3引数であたえた関数を呼び出します。
この例では20.0から50.5の区間を通過しているときに、updatePart関数を呼び出します。
updatePartの第1引数のFloatChangePartは、開始値が0.0、終了値が1.0であるようなFloatChangeです。この場合、元のFloatChange値が20.0のとき0.0、50.5のとき1.0になるように対応させて渡されます。
この時、FloatChangePartのcurrentとpreviousの値が0.0より低い値や、1.0より高い値で、updatePartが呼び出されることはありません。
区間の開始と、終了を取得する
FloatChangePartには、モーションの開始タイミングや、終了タイミングを取得するための関数が用意されています。
private function updatePart(part:FloatChangePart) {
if (part.isEntrance()) {
var topBar = new Square();
addChild(topBar);
topBar.width = 481;
}
square.x = part.current.expoIn().lerp(0, 450);
if (part.isExit()) {
var bottomBar = new Square();
addChild(bottomBar);
bottomBar.y = 120;
bottomBar.width = 481;
}
}
繰り返し
1つのパートを、複数回繰り返したい場合、handlePartの代わりにhandleRepeatPartを使います。
change.handleRepeatPart(20, 40, 3, updatePart);
このサンプルでは、20フレーム目から80フレーム目までの60フレームの間にFloatChangePartの0.0から1.0の移動が3回繰り返されています。
handleRepeatPartがupdateSquareに引数として渡すFloatChangePartはFloatChangeRepeatPartとして拡張したもので、現在が何回目の繰り返しかなどの追加の情報にアクセスができます。
連続したモーションをあつかう
連続したモーションをあつかうには、FloatChangeのhandleTimelinePartが使えます。
右、下、左の三つの移動を行いました。
var timeline:Timeline<FloatChangeTimelinePart->Void>;
public function new() {
// (中略)
// 重み付きのupdate関数の配列を作成。
timeline = new Timeline().add(update1, 1).add(update2, 2).add(update3, 5);
}
public function update():Void {
var floatChange = new FloatChange(frameCount, frameCount += 1);
floatChange.handleTimelinePart(0, 80, timeline);
}
private function update1(part:FloatChangeTimelinePart):Void {
// 右へ移動
square.x = part.current.lerp(0, 450);
}
private function update2(part:FloatChangeTimelinePart):Void {
// 下へ移動
square.y = part.current.cubicInOut().lerp(0, 120);
}
private function update3(part:FloatChangeTimelinePart):Void {
// 左へ移動
square.x = part.current.quartIn().cubicIn().lerp(450, 0);
}
Timelineは重み付きの配列です。配列の各要素にFloatで重みがつけられています。サンプルではupdate1, update2, update3に1:2:5の重みを付けています。
handleTimelinePartは、この重みに従ってupdate関数を呼び出します。サンプルでは、10フレームかけてupdate1を、20フレームかけてupdate2を、50フレームかけてupdate3を呼び出しています。
FloatChangeTimelinePartはFloatChangePartを継承しており、現在タイムラインのどの位置にいるかなどの情報が追加で取得できます。
2次元の動き
単純な動き
いままでのモーションはすべてX方向の動きをあつかってきましたが、Y方向の動きも入れてみます。
square.x = part.current.lerp(0, 450);
square.y = part.current.sinByRate().lerp(60, 105);
四角をY方向に揺らしてみました。ここで使っているsinByRateは円の一周を1.0としてあつかうsin関数です。
このサンプルはそれほど目新しくはありません。問題は同じようなモーションを斜め方向に行う場合です。
相似変換
これまで0.0から1.0の値を実際のx座標に変換するのにはlerp関数を使ってきましたが、回転が加わる場合lerp関数では表現できません。
そういった場合は、MatrixTools.createSimilarityTransformを使います。
private var matrix:MatrixImpl;
public function new() {
// (中略)
// Flashプラットフォームなら、flash.geom.Matrixを使える。
// pixi.jsや、OpenFLのなど場合、それぞれのフレームワークで定義されているMatrixが使える。
// 実際のサンプルコードでは、自前でサンプル用に定義したクラスを使っている。
matrix = new MatrixImpl();
// (0, 0)から(1, 0)への移動を、(100, 0)から(350, 120)への移動に相似変換する行列を作成。
matrix.createSimilarityTransform(100, 0, 350, 120);
}
private function updatePart(part:FloatChangePart):Void {
var x = part.current;
var y = part.current.sinByRate().lerp(0, 0.1);
square.x = matrix.a * x + matrix.c * y + matrix.tx;
square.y = matrix.b * x + matrix.d * y + matrix.ty;
}
createSimilarityTransform(fromX, fromY, toX, toY)関数は、X方向の0.0から1.0までの移動を、X方向にfromXからtoX、Y方向にfromYからtoYの移動に相似変換するような行列を作成します。
極座標
TweenXCoreは極座標をサポートしています。
public function new() {
// (中略)
// 相似変換を作成
matrix = new MatrixImpl();
matrix.createSimilarityTransform(210, 60, 0, 0);
}
private function updatePart(part:FloatChangePart) {
// 原点に近づいていく
var distance = part.current.expoOut().lerp(1, 0);
// 反時計回りに2周する。
var angle = part.current.lerp(0, -2);
// 極座標からXY座標へ変換
var polarPoint = new PolarPoint(distance, angle);
var x = polarPoint.x;
var y = polarPoint.y;
// 実際の座標へ変換
square.x = matrix.a * x + matrix.c * y + matrix.tx;
square.y = matrix.b * x + matrix.d * y + matrix.ty;
}
サンプルは、(210, 60)を極座標の中心として、そこに(0, 0)の位置から回転しながら近づいていくモーションです。
ベジェ曲線
TweenXCoreはベジェ曲線もサポートしています。
square.x = rate.bezier3(0, 50, 400, 450);
square.y = rate.bezier3(0, 200, -50, 120);
サンプルは、始点が(0, 0)、制御点が(50, 200)と(400, -50)、終点が(450, 120)の3次ベジェ曲線です。
ベジェ曲線は3次だけでなく任意の次数ののベジェ曲線が使えます。詳しくはtweenxcore.Toolsモジュールを確認してください。
いろんなものを動かす
RGBカラー、HSVカラー
TweenXCoreでは、RGBカラーとHSVカラーが使えます。
var curve = part.current.expoInOut();
var hue = hsvCurve.lerp(0.0, 1.0); // 色相を1周させる
var saturation = hsvCurve.lerp(0.0, 0.8); // 彩度を上げていく
var value = 0.95; // 明度は固定
var color = new HsvColor(hue, saturation, value);
サンプルはHSVのそれぞれの値を動かしながら帯を描いています。
画像
連続したモーションをあつかうのに重み付き配列のTimelineを紹介しましたが、このTimelineは連続でない値を動かすのにも使えます。
つまり、例えば画像のパラパラアニメーションを作るのにも使えます。
時間以外に基づくトゥイーン
マウス座標を元にトゥイーンさせる
0.0から1.0の値に変換可能であればトゥイーンのもとになる値は、時間でなくても構いません。以下のサンプルでは、マウス座標を元に四角の位置を動かしています。
var rateX = mouseX.inverseLerp(10, 800).clamp(0, 1);
var rateY = mouseY.repeat(0, 400);
square.x = rateX.expoInOut().lerp(0, 450);
square.y = rateY.yoyo(Easing.expoInOut).lerp(0, 120);
inverseLerpは、lerpとは逆向きの線形補間で、例では10から800までの値を0から1に変換しています。
clampは下限と上限を設定する関数で、例では0より小さい値を0に、1より大きい値を1に変換しています。
repeatは繰り返しを行う関数で、まず0から400の値を0から1に変換するところまではinverseLerpと同じですが、0より小さい値や400より大きい値など、指定した間隔の外側での挙動が違います。
つまり、repeatでは-400から0、0から400、400から800、800から1200といった各値で0から1への変換がされます。
ランダム
ゆらす
TweenXCoreは、乱数と合わせて使うことでさまざまな表現を作ることができます。以下は、その一例として四角をゆらす演出をつくっています。
public function update():Void {
var scale = 3;
square1.x = scale.shake( 90);
square1.y = scale.shake( 60);
square2.x = scale.shake(225, random2);
square2.y = scale.shake( 60, random2);
square3.x = scale.shake(360, random3);
square3.y = scale.shake( 60, random3);
}
private static function random2():Float {
// 0と1によせる
return Math.random().quintInOut();
}
private static function random3():Float {
// 0.5によせる
return Math.random().quintOutIn();
}
FloatTools.shakeは、第1引数に揺らす大きさ、第2引数に中心値、第3引数に乱数を生成する関数を取ります。
サンプルでは生成する乱数にイージングをかけることで、揺れ方に変化をあたえています。
