大部分游戏中都有移动模型的需求,要移动模型,首先要获取到模型要走的路线,路线就是一个个节点的坐标构成的数组,然后根据数组中的每一个元素,依次并将模型的位置更新,也就是设置模型的x,y值(相对于屏幕笛卡尔坐标系中的横纵坐标值)。
一般的,想要更加真实的表现出人物移动的效果,可以模拟加速,减速。就需要在设计上加上一个属性。
速度 – speed;
根据 S = V * T; 距离 = 速度 * 时间;可以计算出当前帧移动的距离,根据距离再求出相应x,y轴移动的分量,达到一个移动的目的。
这就是一个根据直角三角形斜边长,和斜边的斜率,求两条直角边的长度的问题。这又涉及到一个求角度的问题。
下面要介绍一个不需要求角度的实现方式。
相似三角形
将当前模型所在位置置为坐标系原点O,B为目标点位置,OB的模长就是当前位置到目标点的距离。
OD是当前帧移动的距离,那么根据上图的辅助线CD,AB易得,
(1)OC的距离是x分量移动的距离,CD的距离是y分量移动的距离;
(2)▲OCD与▲OAB相似;
那么,根据相似三角形的性质,得出
OC / OA = CD / AB = OD / OB;
令D(mx,my),则
mx / dx = my / dy = OD / OB;
OD可以根据时间 * 速度得出,OB可以根据勾股定理求出。
那么OC和CD的距离,也就是mx,my也可求出
令f = OD / OB;
mx = f * dx;
my = f * dy;
这边的代码是根据cocoscreator引擎来写的。这个没有限制,方法是通用的。
具体实现代码如下:
const {ccclass, property} = cc._decorator;
enum Direction{
UP,
UP_RIGHT,
RIGHT,
DOWN_RIGHT,
DOWN,
DOWN_LEFT,
LEFT,
UP_LEFT
}
@ccclass
export default class Avartar extends cc.Component {
start () {
}
/**
* 人物移动的速度
*/
private _speed:number = 10;
get speed(){
return this._speed;
}
/**
* 使用set方法设置speed,可以根据速度的不同值,播放动画不同的动作
* 比如切换行走,跑动和站立的动作
*/
set speed(value:number){
if(value === this._speed)return;
this._speed = value;
}
/**
* 人物当前路径
*/
private _path:cc.Vec2[] = [];
/**
* 当前目标点,是即将要走到的目标点
* 并非最终目的地
*/
private _curTargetPosition:cc.Vec2;
/**
* 模型方向
*/
private _dir:Direction;
get dir(){
return this._dir;
}
/**
* 可以在set方法里做更多关于方向更新的操作
*/
set dir(value:number){
if(value === this._dir)return;
this._dir = value;
}
/**
* cocos提供的回调函数,每帧都会执行
* @param dt 当前帧执行的时间,是执行一帧所需要的时间,单位是秒
* 一般60帧的刷新频率下,dt的值约为1 / 60;
*/
update (dt:number) {
this.loopMove(dt);
}
/**
* 每帧更新移动
* @param dt
*/
loopMove(dt:number){
//当速度为0时,不需要执行下面代码。因为不会移动
if(!this.speed) return;
//速度*时间,求出当前帧移动的距离,就是图中OD的距离
let frameDistance = dt * this.speed;
//求出图中B点的坐标值(dx,dy)
let dx = this._curTargetPosition.x - this.node.x;
let dy = this._curTargetPosition.y - this.node.y;
//求出当前位置与目标位置的距离,也就是图中OB的距离
let distance = Math.sqrt(dx * dx + dy * dy);
// OD / OB,就是相似三角形的比例
let f = frameDistance / distance;
// 根据比例增加坐标值,至此单帧移动完成
this.node.x += f * dx;
this.node.y += f * dy;
//判断是否到达当前目的地,因为js里数字类型都是浮点型保存,所以在判断的时候需要进行取整。
if(this.node.x >> 0 === this._curTargetPosition.x
&& this.node.y === this._curTargetPosition.y){
this.doMoveNextPosition();
}
}
/**
* 移动下一个目标点
*/
doMoveNextPosition(){
if(this._path && this._path.length){
this._curTargetPosition = this._path.shift();
this.speed = 20;
//更换目标点的时候需要更新模型方向
this.updateDir();
}else{
this.speed = 0;
}
}
updateDir(){
this.dir = this.getDirection(this.node.getPosition(),this._curTargetPosition);
}
/**
* 设置路径
* @param path 路径点数组
*/
setPath(path:cc.Vec2[]){
this._path = path;
this.doMoveNextPosition();
}
getDirection(curPoint:cc.Vec2,targetPoint:cc.Vec2){
//先得到当前位置与目标位置形成的向量(dx,dy);
let dx = targetPoint.x - curPoint.x;
let dy = targetPoint.y - curPoint.y;
//当向量的X分量为0时,y分量 > 0则为上方向,反之为下方向,
//因为下方向是正对准屏幕的方向,所以Y分量等于0时也是下方向。让玩家可以直接看到正脸
if(dx === 0){
return dy > 0 ? Direction.UP : Direction.DOWN;
}
//当向量的Y分量为0时,X分量 >= 0则为右向(因为个人觉得右向看起来比较舒服,所以在为0时也是右向)
//反之为左向
if(dy === 0){
return dx >= 0 ? Direction.RIGHT : Direction.LEFT;
}
let ddy = dy / dx;
if(ddy >= 2.414){
return Direction.UP;
}else if(ddy >= 0.414 && ddy < 2.414){
return dx > 0 ? Direction.UP_RIGHT : Direction.UP_LEFT;
}else if(ddy >= -2.414 && ddy < 0.414){
return dx > 0 ? Direction.DOWN_RIGHT : Direction.DOWN_LEFT;
}
return Direction.DOWN;
}
}