此文为翻译文章,文章来源:Phaser Motion Paths
在游戏编程和动画中有很多情况需要运动路径(motion paths),一辆绕着赛道疾驰的赛车或者太阳缓慢上升和降落。实际场景中,我们需要精灵(sprite)跟随预定路径的任何情形都非常适合运动路径。在这篇文章中,我将通过使用 Javascript 和 HTML5 游戏框架 Phaser 解释如何创建运动路径,以及怎样使精灵跟随它们。(同学们,划重点了啊)关键部分位于 Phaser 的 Math 类,更具体的话是指这个 Math 类中的三个方法。
插值方法(The Interpolation Methods)
以下是三个重要的“运动路径”方法:线性插值(linearInterpolation),曲线插值(bezierInterpolation)和Catumll-Rom插值(catmullRomInterpolation)。让我们花点时间来研究这些方法实际能干些什么,关键点在于这些方法并不能为我们神奇的创造运动路径,它们仅仅为我们处理数学(计算)。幸运的是,一旦你看过他们的实际效果,它们就很容易被掌握。
每个插值方法的第一个参数为一组数值构成的数组,第二个参数为 0 到 1 之间的一个数值。你可以认为第一个参数为表示一个路径上的一组点,第二个参数为沿着这条路径的距离。还是用代码让你明白说的是啥意思吧。
代码清单1展示了 math.linearInterpolation
的实际效果。看看代码1中的 create
方法,然后看看代码1产生的结果。基本上,整个代码展示了基于同一数组 points
和不同的变量 distance
调用线性插值的运算。
代码清单1,注意结果数字
// 代码1
var game = new Phaser.Game(640, 420, Phaser.AUTO, 'game');
var PhaserGame = function() {
this.textStyle = {
font: "30px Arial",
fill: "#ff0044",
align: "center"
};
};
PhaserGame.prototype = {
create: function() {
var points = [2, 128, 266, 384, 512, 640];
var distance, result;
distance = .01;
result = this.math.linearInterpolation(points, distance);
game.add.text(20, 20, result, this.textStyle);
distance = .3;
result = this.math.linearInterpolation(points, distance);
game.add.text(20, 70, result, this.textStyle);
distance = .7;
result = this.math.linearInterpolation(points, distance);
game.add.text(20, 120, result, this.textStyle);
distance = 1;
result = this.math.linearInterpolation(points, distance);
game.add.text(20, 170, result, this.textStyle);
}
};
window.onload = function() {
game.state.add('Game', PhaserGame, true);
}
代码清单1运行结果(在线看效果)
注意我们的 points
数组由从 2 开始到 640 结束的一组数值构成。当我们增加变量 distance
的值时(又要划重点了,应为一个 0 到 1 之间的数),我们会“移动”到更高的数值。实际上,取值为 1 时,是这条路径的100%,我们看到数组 points
中的最后一个数是 640,结果的最后一个也是相同的 640。
真正了解 math.linearInterpolation
方法可以通过对代码做些有趣的改变。注意数组 points
目前是简单可预测的。就是说,我们(的数组)开始于一个较小值,并且逐步增长到最大值。这听起来好像一条直线,其实我们并不需要运动路径是一条直线。
var points = [2, 128, 266, 768, 512, 640];
上面的数组 points
从 2 移动 640, 但是注意它中间有一个值为 768 。你可以将这行代码插入代码清单1,或者胡乱修改下已经存在数字,然后注意观察对结果数字的影响。
绘制运动路径(Drawing Motion Paths)
Ok,以上就是对 interpolation
方法的一点了解,但是我们如何使用它来指导精灵运动?简单而言,我们可以使用 interpolation
方法帮助我们(产生)我们精灵的坐标 x 和 y。让我们把它分解成更容易理解的2个不同部分:创建运动路径,然后让精灵跟随它。
创建运动路径(Creating a Motion Path)
看看代码清单2,然后我们会重点讲解。这个清单展示了如何在屏幕上绘制一条运动路径。当然,你通常不想让最终用户看到这条运动路径,但是在这个例子里,(运动路径)将会帮助我们可视化只能看到数字的代码清单1的运行结果。
代码清单2,绘制一个运动路径
var game = new Phaser.Game(640, 480, Phaser.AUTO, 'game');
var PhaserGame = function() {
this.bmd = null;
// points arrays - one for x and one for y
this.points = {
'x': [0, 200, 120, 456, 640],
'y': [0, 300, 120, 156, 480]
};
};
PhaserGame.prototype = {
create: function() {
this.stage.backgroundColor = '#eee';
this.increment = 1 / game.width;
this.i = 0;
// 绘制这个路径
this.bmd = this.add.bitmapData(this.game.width, this.game.height);
this.bmd.addToWorld();
for (var i = 0; i < 1; i += this.increment) {
var px = this.math.linearInterpolation(this.points.x, i);
var py = this.math.linearInterpolation(this.points.y, i);
this.bmd.rect(px, py, 3, 3, 'rgba(245, 0, 0, 1)');
}
}
};
window.onload = function() {
game.state.add('Game', PhaserGame, true);
}
代码清单2运行结果(在线看效果)
敲黑板了,这个例子中的 points
来自 2 个数组:一个是 x 的坐标序列,一个是 y 的坐标序列。我尝试用这些值获得我喜欢的东西,我原本就确定起点在屏幕的左上角(注意 x 和 y 数组的第一个位置的 0 ),终点在屏幕的右下角(注意 x 和 y 数组的最后一个位置的游戏的 width 和 height ), 当然,你可以用你想得到结果的任何值。
注意 this.increment
这个变量,我们用 1 除以我们的游戏宽度 480, 然后存储到该变量中。这个给了我们一个很小的数值,0.002083
。我们的 for
循环执行了 480 次,每次都会绘制一个基于路径的小矩形。
方法 math.linearInterpolation
使用数组 points
和基于 this.increment
的当前值帮助我们计算在何处绘制。
我建议你修改这个代码清单然后继续(跟着这篇教程走)。尝试修改数组 points
的 2 个(坐标序列的)值,然后看看当你把 math.linearInterpolation
改为 math.bezierInterpolation
和 math.catmullRomInterpolation
后会发生什么。
使精灵跟随运动路径
基于我们到目前为止所知道的,接下来并不会太难,使用插值逻辑(interpolation logic)替代绘制(运动路径)线,可以让精灵(sprite)在一段时间内在屏幕上移动。下面这个代码清单就是这样干的,需要特比注意下 plot
方法。
代码清单2,附加精灵到运动路径(ps:不应该是代码清单3么。。。)
var game = new Phaser.Game(640, 480, Phaser.AUTO, 'game');
var PhaserGame = function() {
this.bmd = null;
// 数组 points - x 坐标序列 和 y 坐标序列
this.points = {
'x': [0, 200, 120, 456, 640],
'y': [0, 300, 120, 156, 480]
};
};
PhaserGame.prototype = {
preload: function() {
game.load.crossOrigin = "Anonymous";
game.load.image('bird', 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/173252/angry-bird.png');
},
create: function() {
this.stage.backgroundColor = '#eee';
this.increment = 1 / game.width;
this.i = 0;
this.timer1Stopped = true;
this.timer1 = null;
// 用来绘制(运动路径)
this.bmd = this.add.bitmapData(this.game.width, this.game.height);
this.bmd.addToWorld();
// 绘制路径
for (var j = 0; j < 1; j += this.increment) {
var posx = this.math.linearInterpolation(this.points.x, j);
var posy = this.math.linearInterpolation(this.points.y, j);
this.bmd.rect(posx, posy, 3, 3, 'rgba(245, 0, 0, 1)');
}
// 创建小鸟精灵 - 我们将让这个精灵
// 通过使用 plot 方法跟随运动路径
this.birdSprite = game.add.sprite(0, 0, "bird");
this.birdSprite.anchor.setTo(0.5, 0.5);
},
update: function() {
// 这只是设置重置
// 定时器,以便重复移动
if (this.timer1Stopped) {
this.timer1Stopped = false;
this.timer1 = this.game.time.create(true);
this.timer1.loop(.01, this.plot, this);
this.timer1.start();
}
},
plot: function() {
var posx = this.math.linearInterpolation(this.points.x, this.i);
var posy = this.math.linearInterpolation(this.points.y, this.i);
this.birdSprite.x = posx;
this.birdSprite.y = posy;
this.i += this.increment;
if (posy > 480) {
this.timer1.stop();
this.timer1.destroy();
this.i = 0;
this.timer1Stopped = true;
}
}
};
window.onload = function() {
game.state.add('Game', PhaserGame, true);
}
代码清单2运行结果(在线看效果)
上面代码大部分是模式化的,再加上一些定时器(timer)的管理逻辑(因此动画能够重复)。你应该感兴趣的部分在于:
var px = this.math.linearInterpolation(this.points.x, this.i);
var py = this.math.linearInterpolation(this.points.y, this.i);
this.birdSprite.x = px;
this.birdSprite.y = py;
this.i += this.increment;
确保你能愉快的理解 this.i
和 this.increment
之间的关系。前者从 0 开始,每次调用 plot
最后,让我们仅仅改变插值方法(interpolation methods)其余内容不变之后的结果。
代码清单3,Bezier 插值
var game = new Phaser.Game(640, 480, Phaser.AUTO, 'game');
var PhaserGame = function() {
this.bmd = null;
// points arrays - one for x and one for y
this.points = {
'x': [0, 200, 120, 456, 640],
'y': [0, 300, 120, 156, 480]
};
};
PhaserGame.prototype = {
preload: function() {
game.load.crossOrigin = "Anonymous";
game.load.image('bird', 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/173252/angry-bird.png');
},
create: function() {
this.stage.backgroundColor = '#eee';
this.increment = 1 / game.width;
this.i = 0;
this.timer1Stopped = true;
this.timer1 = null;
// Somewhere to draw to
this.bmd = this.add.bitmapData(this.game.width, this.game.height);
this.bmd.addToWorld();
// Draw the path
for (var j = 0; j < 1; j += this.increment) {
var posx = this.math.bezierInterpolation(this.points.x, j);
var posy = this.math.bezierInterpolation(this.points.y, j);
this.bmd.rect(posx, posy, 3, 3, 'rgba(245, 0, 0, 1)');
}
// create the bird sprite - we will make this sprite
// follow the motion path by using the plot function
this.birdSprite = game.add.sprite(0, 0, "bird");
this.birdSprite.anchor.setTo(0.5, 0.5);
},
update: function() {
// this just takes care of resetting
// the timer so the movement repeats
if (this.timer1Stopped) {
this.timer1Stopped = false;
this.timer1 = this.game.time.create(true);
this.timer1.loop(.01, this.plot, this);
this.timer1.start();
}
},
plot: function() {
var posx = this.math.bezierInterpolation(this.points.x, this.i);
var posy = this.math.bezierInterpolation(this.points.y, this.i);
this.birdSprite.x = posx;
this.birdSprite.y = posy;
this.i += this.increment;
if (posy > 480) {
this.timer1.stop();
this.timer1.destroy();
this.i = 0;
this.timer1Stopped = true;
}
}
};
window.onload = function() {
game.state.add('Game', PhaserGame, true);
}
代码清单3运行结果(在线看效果)
代码清单4. CatmullRom 插值
var game = new Phaser.Game(640, 480, Phaser.AUTO, 'game');
var PhaserGame = function() {
this.bmd = null;
// points arrays - one for x and one for y
this.points = {
'x': [0, 200, 120, 456, 640],
'y': [0, 300, 120, 156, 480]
};
};
PhaserGame.prototype = {
preload: function() {
game.load.crossOrigin = "Anonymous";
game.load.image('bird', 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/173252/angry-bird.png');
},
create: function() {
this.stage.backgroundColor = '#eee';
this.increment = 1 / game.width;
this.i = 0;
this.timer1Stopped = true;
this.timer1 = null;
// Somewhere to draw to
this.bmd = this.add.bitmapData(this.game.width, this.game.height);
this.bmd.addToWorld();
// Draw the path
for (var j = 0; j < 1; j += this.increment) {
var posx = this.math.bezierInterpolation(this.points.x, j);
var posy = this.math.bezierInterpolation(this.points.y, j);
this.bmd.rect(posx, posy, 3, 3, 'rgba(245, 0, 0, 1)');
}
// create the bird sprite - we will make this sprite
// follow the motion path by using the plot function
this.birdSprite = game.add.sprite(0, 0, "bird");
this.birdSprite.anchor.setTo(0.5, 0.5);
},
update: function() {
// this just takes care of resetting
// the timer so the movement repeats
if (this.timer1Stopped) {
this.timer1Stopped = false;
this.timer1 = this.game.time.create(true);
this.timer1.loop(.01, this.plot, this);
this.timer1.start();
}
},
plot: function() {
var posx = this.math.catmullRomInterpolation(this.points.x, this.i);
var posy = this.math.catmullRomInterpolation(this.points.y, this.i);
this.birdSprite.x = posx;
this.birdSprite.y = posy;
this.i += this.increment;
if (posy > 480) {
this.timer1.stop();
this.timer1.destroy();
this.i = 0;
this.timer1Stopped = true;
}
}
};
window.onload = function() {
game.state.add('Game', PhaserGame, true);
}
代码清单4运行结果(在线看效果)
我希望你发现这篇文章对你有用且易于理解。如果不是太繁忙的话,建议修改(上面的)代码清单。虽然(代码清单)很简单,但是还是有一些可改动的部分。不要对修改后的结果感到过于惊讶。
好棒啊
请问楼主如何计算两个点抛物线形式移动呢![,
就和“猪来了”,那种游戏的弹弓效果