精华 Phaser 运动路径
发布于 2 年前 作者 lazyboywu 1103 次浏览 来自 分享

此文为翻译文章,文章来源: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运行结果(在线看效果 代码清单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运行结果(在线看效果 代码清单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.bezierInterpolationmath.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运行结果(在线看效果 GIF1.gif

上面代码大部分是模式化的,再加上一些定时器(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.ithis.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运行结果(在线看效果 代码清单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运行结果(在线看效果 代码清单4运行结果

我希望你发现这篇文章对你有用且易于理解。如果不是太繁忙的话,建议修改(上面的)代码清单。虽然(代码清单)很简单,但是还是有一些可改动的部分。不要对修改后的结果感到过于惊讶。

2 回复

请问楼主如何计算两个点抛物线形式移动呢![QQ截图20170827105208.png,

就和“猪来了”,那种游戏的弹弓效果

回到顶部