轉(zhuǎn)向行為! steering behaviors !
尋找 seek 避開 flee 到達(dá) arrive 追逐 purse 躲避 evade 群落 flock !
效果
轉(zhuǎn)向行為旨在通過使用簡單的力來幫助自主角色以逼真的方式運(yùn)動(dòng),這些簡單的力結(jié)合起來可以圍繞角色的環(huán)境產(chǎn)生逼真的,即興的導(dǎo)航。它們不是基于涉及路徑規(guī)劃或全局計(jì)算的復(fù)雜策略,而是使用本地信息,例如鄰居的部隊(duì)。這使它們易于理解和實(shí)施,但仍然能夠產(chǎn)生非常復(fù)雜的運(yùn)動(dòng)模式。
群落效果:
追逐偏移:
視頻預(yù)覽[歡迎關(guān)注,獲得最新研究成果]:
實(shí)現(xiàn)
實(shí)現(xiàn)轉(zhuǎn)向行為抓住以下兩個(gè)點(diǎn):
轉(zhuǎn)向力 周圍角色對(duì)象
角色類需要包含以下幾個(gè)基本屬性:
位置 速度 轉(zhuǎn)向力
每幀根據(jù)合力與加速度,速度與位置的關(guān)系,更新位置。

this._steeredForce.multiplyScalar(1 / this.mass)
this.velocity.add(this._steeredForce)
this._steeredForce.set(0, 0)
this._position.add(this.velocity)
每種行為,都會(huì)算出有個(gè)期望速度,根據(jù)這個(gè)期望速度算出一個(gè)轉(zhuǎn)向力加上去即可。
尋找 seek 與 避開 flee
尋找的期望速度直接指向目標(biāo)點(diǎn),轉(zhuǎn)向力是期望速度減去當(dāng)前速度。避開正好相反。

seek(target: Vec2): void {
const desiredVelocity: Vec2 = Vec2.subtract(temp_v2, target, this._position).normalize()
desiredVelocity.multiplyScalar(this.maxSpeed);
const force: Vec2 = desiredVelocity.subtract(this.velocity);
this._steeredForce.add(force);
}
flee(target: Vec2): void {
const desiredVelocity: Vec2 = Vec2.subtract(temp_v2, target, this._position).normalize()
desiredVelocity.multiplyScalar(this.maxSpeed);
const force: Vec2 = desiredVelocity.subtract(this.velocity);
this._steeredForce.subtract(force);
}
到達(dá) arrive
與尋找的期望速度類似,方向是一樣的,區(qū)別是快到的時(shí)候速度會(huì)減少。

可以加一個(gè)距離控制,在還沒達(dá)到這個(gè)距離時(shí),先以最快的速度過去,比較近的時(shí)候減緩速度。
arrive(target: Vec2): void {
const desiredVelocity: Vec2 = Vec2.subtract(temp_v2, target, this._position).normalize()
const dist: number = Vec2.distance(this._position, target)
if (dist > this.arrivalThreshold) {
desiredVelocity.multiplyScalar(this.maxSpeed);
} else {
desiredVelocity.multiplyScalar(this.maxSpeed * dist / this.arrivalThreshold);
}
const force: Vec2 = desiredVelocity.subtract(this.velocity);
this._steeredForce.add(force);
}
追逐 purse 與 躲避 evade
追逐與躲避會(huì)預(yù)測(cè)目標(biāo)將會(huì)移動(dòng)到的位置,最后再調(diào)用尋找和避開。

其中,追逐時(shí),如果剛好在正前方,就不用預(yù)測(cè)目標(biāo)移動(dòng)的位置,直接飛過去就好。
pursue(target: Vehicle): void {
const toTarget = Vec2.subtract(temp_v2, target.position, this._position)
if (toTarget.dot(this.heading) > 0 && this.heading.dot(target.heading) < -0.95) {
// 如果面對(duì)面,正好在前面,就直接飛過去
this.seek(target.position)
} else {
const lookAheadTime: number = Vec2.distance(this._position, target.position) / (this.maxSpeed + target.velocity.length());
const predictedTarget: Vec2 = Vec2.add(temp2_v2, target.position, Vec2.multiplyScalar(temp_v2, target.velocity, lookAheadTime));
this.seek(predictedTarget)
}
}
evade(target: Vehicle): void {
const lookAheadTime: number = Vec2.distance(this._position, target.position) / (this.maxSpeed + target.velocity.length())
const predictedTarget: Vec2 = Vec2.add(temp2_v2, target.position, Vec2.multiplyScalar(temp_v2, target.velocity, lookAheadTime));
this.flee(predictedTarget)
}
追逐偏移 pursueOffset
追逐偏移使得角色之間保持指定位置的偏移。

先初始算出偏移位置,再算偏移預(yù)期位置,最后調(diào)用到達(dá)。
pursueOffset(target: Vehicle, offset: Vec2): void {
const localOffset = temp_v2.set(
target.side.x * offset.x + target.side.y * offset.y,
target.heading.x * offset.x + target.heading.y * offset.y
)
const offsetTargetPos = Vec2.add(temp_v2, target.position, localOffset)
const lookAheadTime: number = Vec2.distance(this._position, offsetTargetPos) / (this.maxSpeed + target.velocity.length())
const predictedTarget: Vec2 =
Vec2.add(
temp2_v2,
offsetTargetPos,
Vec2.multiplyScalar(temp2_v2, target.velocity, lookAheadTime),
);
this.arrive(predictedTarget)
}
群落 flock
群落行為是由分離、凝聚和隊(duì)列組成。
分離(separation):每個(gè)角色都試著和相鄰角色保持一定的距離。 凝聚(cohesion):每個(gè)角色盡量不掉隊(duì),不落下太遠(yuǎn)。 隊(duì)列(alignment):每個(gè)角色盡可能與相鄰角色行動(dòng)于同一方向。

近了就離開,在一定范圍內(nèi)就靠近,速度取平均值。
flock(vehicles: Vehicle[]): void {
let averageVelocity: Vec2 = temp3_v2.set(this.velocity)
let averagePosition: Vec2 = temp4_v2.set(0, 0)
let inSightCount = 0;
for (let i = 0; i < vehicles.length; i++) {
let vehicle: Vehicle = vehicles[i] as Vehicle;
if (vehicle != this && this.inSight(vehicle)) {
averageVelocity = averageVelocity.add(vehicle.velocity);
averagePosition = averagePosition.add(vehicle.position);
if (Vec2.squaredDistance(this.position, vehicle.position) < this.tooCloseDist * this.tooCloseDist) this.flee(vehicle.position);
inSightCount++;
}
}
if (inSightCount > 0) {
averageVelocity.multiplyScalar(1 / inSightCount);
averagePosition.multiplyScalar(1 / inSightCount);
this.seek(averagePosition);
const force = averageVelocity;
this._steeredForce.subtract(force);
}
}
小結(jié)
轉(zhuǎn)向行為的實(shí)現(xiàn)可分解為模擬各個(gè)行為的力,再計(jì)算合力,接著算加速度和速度,最后更新位置!
以上為白玉無冰使用 Cocos Creator 3.1.0 實(shí)現(xiàn) "轉(zhuǎn)向行為!" 的技術(shù)分享。
Cocos Store 鏈接: http://store.cocos.com/app/detail/2893
參考資料
https://www.red3d.com/cwr/steer/
《Flash ActionScript 3.0 動(dòng)畫高級(jí)教程》
《游戲人工智能編程案例精粹》
keep hungry! keep foolish!
更多
折紙效果
豎直布局的文本
彈性跟隨相機(jī)!
標(biāo)志板!
2D 素材 3D 效果!
2020 原創(chuàng)精選!
更多精彩歡迎關(guān)注微信公眾號(hào)
