天天看點

2D遊戲中角色模型移動實作方式

大部分遊戲中都有移動模型的需求,要移動模型,首先要擷取到模型要走的路線,路線就是一個個節點的坐标構成的數組,然後根據數組中的每一個元素,依次并将模型的位置更新,也就是設定模型的x,y值(相對于螢幕笛卡爾坐标系中的橫縱坐标值)。

一般的,想要更加真實的表現出人物移動的效果,可以模拟加速,減速。就需要在設計上加上一個屬性。

速度 – speed;

根據 S = V * T; 距離 = 速度 * 時間;可以計算出目前幀移動的距離,根據距離再求出相應x,y軸移動的分量,達到一個移動的目的。

這就是一個根據直角三角形斜邊長,和斜邊的斜率,求兩條直角邊的長度的問題。這又涉及到一個求角度的問題。

下面要介紹一個不需要求角度的實作方式。

相似三角形

2D遊戲中角色模型移動實作方式

将目前模型所在位置置為坐标系原點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;
    }

}

           

繼續閱讀