egret-貝塞爾曲線實現游戲里物體的曲線移動
1.Bezier曲線的原理
Bezier曲線是應用於二維圖形的曲線。曲線由頂點和控制點組成,通過改變控制點坐標可以改變曲線的形狀。
一次Bezier曲線是由P0至P1的連續點,描述的一條線段
二次Bezier曲線公式
二次Bezier曲線是 P0至P1 的連續點Q0和P1至P2 的連續點Q1 組成的線段上的連續點B(t),描述一條拋物線。
三次Bezier曲線公式:
Bezier曲線二次跟三次的實現,其實就是根據幾個連續點,還有給定的t,繪制出點。
class BezierUtil { /** * 根據傳入的錨點組返回貝塞爾曲線上的一組點,返回類型為egret.Point[]; * @param pointsData 錨點組,保存着所有控制點的x和y坐標,格式為[x0,y0,x1,y1,x2,y2...] * @param pointsAmount 要獲取的點的總個數,實際返回點數不一定等於該屬性,與范圍有關 * @returns egret.Point[]; */ public static createBezierPoints(pointsData: number[], pointsAmount: number): egret.Point[] { let points = []; for (let i = 0; i < pointsAmount; i++) { const point = this.getBezierPointByFactor(pointsData, i / pointsAmount); if (point) points.push(point); } return points; } /** * 根據錨點組與取值系數獲取貝塞爾曲線上的一點 * @param pointsData 錨點組,保存着所有控制點的x和y坐標,格式為[x0,y0,x1,y1,x2,y2...] * @param t 取值系數 * @returns egret.Point */ public static getBezierPointByFactor(pointsData: number[], t: number): egret.Point { let i = 0; let x = 0, y = 0; const len = pointsData.length; //根據傳入的數據數量判斷是二次貝塞爾還是三次貝塞爾 if (len / 2 == 3) { //二次 const x0 = pointsData[i++]; const y0 = pointsData[i++]; const x1 = pointsData[i++]; const y1 = pointsData[i++]; const x2 = pointsData[i++]; const y2 = pointsData[i++]; x = this.getCurvePoint(x0, x1, x2, t); y = this.getCurvePoint(y0, y1, y2, t); } else if (len / 2 == 4) { //三次 const x0 = pointsData[i++]; const y0 = pointsData[i++]; const x1 = pointsData[i++]; const y1 = pointsData[i++]; const x2 = pointsData[i++]; const y2 = pointsData[i++]; const x3 = pointsData[i++]; const y3 = pointsData[i++]; x = this.getCubicCurvePoint(x0, x1, x2, x3, t); y = this.getCubicCurvePoint(y0, y1, y2, y3, t); } return egret.Point.create(x, y); } /** * 通過factor參數獲取二次貝塞爾曲線上的位置 * 公式為B(t) = (1-t)^2 * P0 + 2t(1-t) * P1 + t^2 * P2 * @param value0 P0 * @param value1 P1 * @param value2 P2 * @param factor t,從0到1的閉區間 */ public static getCurvePoint(value0: number, value1: number, value2: number, factor: number): number { const result = Math.pow((1 - factor), 2) * value0 + 2 * factor * (1 - factor) * value1 + Math.pow(factor, 2) * value2; return result; } /** * 通過factor參數獲取三次貝塞爾曲線上的位置 * 公式為B(t) = (1-t)^3 * P0 + 3t(1-t)^2 * P1 + 3t^2 * (1-t) t^2 * P2 + t^3 *P3 * @param value0 P0 * @param value1 P1 * @param value2 P2 * @param value3 P3 * @param factor t,從0到1的閉區間 */ public static getCubicCurvePoint(value0: number, value1: number, value2: number, value3: number, factor: number): number { const result = Math.pow((1 - factor), 3) * value0 + 3 * factor * Math.pow((1 - factor), 2) * value1 + 3 * (1 - factor) * Math.pow(factor, 2) * value2 + Math.pow(factor, 3) * value3; return result; } }
2.游戲實現
根據Bezier曲線的繪制的點擊,再幀循環改變物體的位置,就可以給出曲線移動的效果。
這里demo里的三個點分別為(100,100),(500,200),(300,800)
class BezierDemoView extends eui.Component { private ball: egret.Shape = new egret.Shape();//小球 private points: egret.Point[] = [];//點集 private pIndex: number = 0;//標記小球當前對應的點的下標 constructor() { super(); this.addEventListener(eui.UIEvent.COMPLETE, this.onUIComplete, this); this.skinName = "BezierDemoViewSkin"; } private onUIComplete(): void { this.points = this.drawPoints(); this.ball.graphics.beginFill(0xbc89f3); this.ball.graphics.drawCircle(0, 0, 10); this.ball.graphics.endFill(); this.addChild(this.ball); this.addEventListener(egret.Event.ENTER_FRAME, this.onUpdate, this); } /** 繪制出點集 */ private drawPoints(): egret.Point[] { let pointData: number[] = [100, 100, 500, 200, 300, 800]; let points: egret.Point[] = BezierUtil.createBezierPoints(pointData, 60); let len: number = points.length; for (let i: number = 0; i < len; i++) { let point: egret.Point = points[i]; let shp: egret.Shape = new egret.Shape(); let gp: egret.Graphics = shp.graphics; gp.beginFill(0xfe0035); gp.drawCircle(point.x, point.y, 5); gp.endFill(); this.addChild(shp); } return points; } /** 幀循環,更新小球位置 */ private onUpdate(): void { let point: egret.Point = this.points[this.pIndex]; if (point) { this.ball.x = point.x; this.ball.y = point.y; this.pIndex++; } else { this.removeEventListener(egret.Event.ENTER_FRAME, this.onUpdate, this); } } }
效果