大部分遊戲中都有移動模型的需求,要移動模型,首先要擷取到模型要走的路線,路線就是一個個節點的坐标構成的數組,然後根據數組中的每一個元素,依次并将模型的位置更新,也就是設定模型的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;
}
}