Phaser 3 : リボントレイルを作る③ tween.updateToを使ってメッシュをアニメーションさせる
前回に引き続きリボントレイルエフェクトを作っていきます。
パスの制御点を渡し変形できるようにする
パスの制御点を操作してリアルタイムにパスを変形できるようにしたいと思います。
公式のサンプル内にドラッグによる変形のサンプルが複数あります。
パスを変形するためにははじめにVector2オブジェクトを渡す必要があるようで、作成後のパスから制御点を取得することはできないようです。
始点、終点、二つの中間制御点を設定してcubicBazierを作成します。
//各ポイントの設定 public tail: Phaser.Math.Vector2; public head: Phaser.Math.Vector2; public controlPoint1: Phaser.Math.Vector2; public controlPoint2: Phaser.Math.Vector2; public CreateCurve() { //制御点を作成 this.tail = new Phaser.Math.Vector2(100, 100); this.head = new Phaser.Math.Vector2(500, 500); this.controlPoint1 = new Phaser.Math.Vector2(Phaser.Math.Between(this.tail.x, this.head.x), Phaser.Math.Between(this.tail.y, this.head.y)); this.controlPoint2 = new Phaser.Math.Vector2(Phaser.Math.Between(this.controlPoint1.x, this.head.x), Phaser.Math.Between(this.controlPoint1.x, this.head.y)); this.curve = new Phaser.Curves.CubicBezier(this.tail, this.controlPoint1, this.controlPoint2, this.head); this.graphics = this.scene.add.graphics(); }
制御点にtweenを設定する
次は各制御点にtweenを設定します。
ひとまずheadをめがけて各制御点を連動させてtweenさせるようにしてみました。
//ポイント用のtween public tweens: Phaser.Tweens.Tween[]=[]; public t1: Phaser.Tweens.Tween public t2: Phaser.Tweens.Tween public t3: Phaser.Tweens.Tween public t4: Phaser.Tweens.Tween ~~~ //各制御点にtweenを作成する this.t1 = this.scene.tweens.add({ targets: this.tail, x: this.controlPoint1.x, y: this.controlPoint1.y, duration: 5000, repeat: -1 }) this.t2 = this.scene.tweens.add({ targets: this.controlPoint1, x: this.controlPoint2.x, y: this.controlPoint2.y, duration: 5000, repeat: -1 }) this.t3 = this.scene.tweens.add({ targets: this.controlPoint2, x: this.head.x, y: this.head.y, duration: 5000, repeat: -1 })
updateにメッシュ変形用の処理を記述します。
update() { //制御点のtweenをアップデートする this.curvePoints = this.curve.getPoints(100); this.mesh.vertices = Float32Array.from(this.UpdateVerts(5, this.points)); }
実行結果がこちらです。 アニメーションの質はともかく、パスのtweenに沿ってメッシュが変形しているのがわかると思います。
tween.updateToを使う
tweenは作成時の値を参照するので、「制御点が次の制御点をめがけてtweenする」といった処理を書いた場合、制御点のtween中の座標めがけてtweenしてくれる、といった挙動にはなりません。
tween.updateToを用いれば、tweenの目標値をupdateで更新することができます。
update() { //制御点のtweenをアップデートする this.t1.updateTo("x", this.controlPoint1.x, true) this.t1.updateTo("y", this.controlPoint1.y, true) this.t2.updateTo("x", this.controlPoint2.x, true) this.t2.updateTo("y", this.controlPoint2.y, true) this.t3.updateTo("x", this.head.x, true) this.t3.updateTo("y", this.head.y, true) this.t4.updateTo("x", this.scene.input.x, true) this.t4.updateTo("y", this.scene.input.y, true) this.curvePoints = this.curve.getPoints(100); this.mesh.vertices = Float32Array.from(this.UpdateVerts(5, this.points)); }
マウスの移動に追従してメッシュが軌跡を描いているのがわかると思います。
しかしなめらかなトレイルの描き方の実装にはもう少し調査が必要そうです。
今回のソース全文はこちらです。
/// <reference path="../app.ts" /> namespace MyGame { interface XY { x: number; y: number; } export class Trail extends Phaser.GameObjects.Container { //グラフィックオブジェクトを用意 public graphics: Phaser.GameObjects.Graphics; public mesh: Phaser.GameObjects.Mesh; public curve: Phaser.Curves.CubicBezier; public curvePoints: Phaser.Math.Vector2[] = []; public drawDebug: boolean = false; public points: number = 100; //各ポイントの設定 public tail: Phaser.Math.Vector2; public head: Phaser.Math.Vector2; public controlPoint1: Phaser.Math.Vector2; public controlPoint2: Phaser.Math.Vector2; //ポイント用のtween public tweens: Phaser.Tweens.Tween[]=[]; public t1: Phaser.Tweens.Tween public t2: Phaser.Tweens.Tween public t3: Phaser.Tweens.Tween public t4: Phaser.Tweens.Tween constructor(scene: Phaser.Scene, x?: number, y?: number, children?: Phaser.GameObjects.GameObject[]) { super(scene, x, y, children); } Init() { this.CreateCurve(); this.curvePoints = this.curve.getPoints(this.points); //頂点配列の作成 let array = this.CreateVerts(5, this.points); //uv配列の作成 let uv = this.CreateUVs(array.length / 12); this.mesh = this.scene.make.mesh({ key: 'block', x: 0, y: 0, vertices: array, uv: uv, }); } public CreateVerts(width: number, points: number): number[] { let arr = []; let pointArray: Phaser.Geom.Point[][] = new Array(); for (let i = 0; i < points; i++) { let mul = width let vecA = new Phaser.Math.Vector2(this.curvePoints[i + 1].x - this.curvePoints[i].x, this.curvePoints[i + 1].y - this.curvePoints[i].y).normalize(); var point1 = new Phaser.Geom.Point(-vecA.y * mul + this.curvePoints[i + 1].x, vecA.x * mul + this.curvePoints[i + 1].y); var point2 = new Phaser.Geom.Point(vecA.y * mul + this.curvePoints[i + 1].x, -vecA.x * mul + this.curvePoints[i + 1].y); pointArray.push([point1, point2]); } for (let i = 0; i < pointArray.length - 1; i++) { //左上 arr.push(pointArray[i + 1][0].x, pointArray[i + 1][0].y); //左下 arr.push(pointArray[i][0].x, pointArray[i][0].y); //右下 arr.push(pointArray[i][1].x, pointArray[i][1].y); //左上 arr.push(pointArray[i + 1][0].x, pointArray[i + 1][0].y); //右下 arr.push(pointArray[i][1].x, pointArray[i][1].y); //右上 arr.push(pointArray[i + 1][1].x, pointArray[i + 1][1].y); } return arr; } public UpdateVerts(width: number, points: number): number[] { let arr = []; let pointArray: Phaser.Geom.Point[][] = new Array(); for (let i = 0; i < points; i++) { let mul = width let vecA = new Phaser.Math.Vector2(this.curvePoints[i + 1].x - this.curvePoints[i].x, this.curvePoints[i + 1].y - this.curvePoints[i].y).normalize(); var point1 = new Phaser.Geom.Point(-vecA.y * mul + this.curvePoints[i + 1].x, vecA.x * mul + this.curvePoints[i + 1].y); var point2 = new Phaser.Geom.Point(vecA.y * mul + this.curvePoints[i + 1].x, -vecA.x * mul + this.curvePoints[i + 1].y); pointArray.push([point1, point2]); } for (let i = 0; i < pointArray.length - 1; i++) { //左上 arr.push(pointArray[i + 1][0].x, pointArray[i + 1][0].y); //左下 arr.push(pointArray[i][0].x, pointArray[i][0].y); //右下 arr.push(pointArray[i][1].x, pointArray[i][1].y); //左上 arr.push(pointArray[i + 1][0].x, pointArray[i + 1][0].y); //右下 arr.push(pointArray[i][1].x, pointArray[i][1].y); //右上 arr.push(pointArray[i + 1][1].x, pointArray[i + 1][1].y); } return arr; } public CreateUVs(points: number): number[] { let arr = []; let uvincr = 1 / points; for (let i = 0; i < points; i++) { //左上 arr.push(0); arr.push(uvincr * i); //左下 arr.push(0); arr.push(uvincr * (i + 1)); //右下 arr.push(1); arr.push(uvincr * (i + 1)); //左上 arr.push(0); arr.push(uvincr * i); //右下 arr.push(1); arr.push(uvincr * (i + 1)); //右上 arr.push(1); arr.push(uvincr * i); } return arr; } public CreateCurve() { //制御点を作成 this.tail = new Phaser.Math.Vector2(100, 100); this.head = new Phaser.Math.Vector2(500, 500); this.controlPoint1 = new Phaser.Math.Vector2(Phaser.Math.Between(this.tail.x, this.head.x), Phaser.Math.Between(this.tail.y, this.head.y)); this.controlPoint2 = new Phaser.Math.Vector2(Phaser.Math.Between(this.controlPoint1.x, this.head.x), Phaser.Math.Between(this.controlPoint1.x, this.head.y)); this.curve = new Phaser.Curves.CubicBezier(this.tail, this.controlPoint1, this.controlPoint2, this.head); this.graphics = this.scene.add.graphics(); //各制御点にtweenを作成する this.t1 = this.scene.tweens.add({ targets: this.tail, x: this.controlPoint1.x, y: this.controlPoint1.y, duration: 5000, repeat: -1 }) this.t2 = this.scene.tweens.add({ targets: this.controlPoint1, x: this.controlPoint2.x, y: this.controlPoint2.y, duration: 5000, repeat: -1 }) this.t3 = this.scene.tweens.add({ targets: this.controlPoint2, x: this.head.x, y: this.head.y, duration: 5000, repeat: -1 }) this.t4 = this.scene.tweens.add({ targets: this.head, x: this.scene.input.x, y: this.scene.input.y, duration: 5000, repeat: -1 }) } update() { //制御点のtweenをアップデートする this.t1.updateTo("x", this.controlPoint1.x, true) this.t1.updateTo("y", this.controlPoint1.y, true) this.t2.updateTo("x", this.controlPoint2.x, true) this.t2.updateTo("y", this.controlPoint2.y, true) this.t3.updateTo("x", this.head.x, true) this.t3.updateTo("y", this.head.y, true) this.t4.updateTo("x", this.scene.input.x, true) this.t4.updateTo("y", this.scene.input.y, true) this.curvePoints = this.curve.getPoints(100); this.mesh.vertices = Float32Array.from(this.UpdateVerts(5, this.points)); } } export class MyScene1 extends Phaser.Scene { //グラフィックオブジェクトを用意 public trail: Trail; constructor() { super({ key: 'MyScene1', active: false }); } preload() { this.load.image("block","assets/images/block.png") } create() { this.trail = new Trail(this); this.trail.Init(); this.cameras.main.setBackgroundColor(0xffffff); this.add.existing(this.trail); this.add.group(this.trail, { runChildUpdate: true }) } } }