「我們不能漫無目的地追,要擬定包夾計畫!」Rain 大聲地說,並展露出大哥是對的姿態。
「我去魔幻水晶的地方埋伏。」Storm 自告奮勇地說。
「那我到處偵查。」Lightning 天生就是偵察兵的料。
「好,那我來找尋那個傢伙的足跡。話說,你們有看到 Snow 跑去哪了嗎?」
PS. 這裡是開發 iOS 手機遊戲的系列文,如果還沒看過之前
劇情文章的朋友,歡迎先點這邊回顧唷!
updateMode
,先在裡頭寫上攻擊的模式對應的目標物格子,其他的模式未來再慢慢補上就好setTarget
方法,改變怪物的屬性 targetGridX
、targetGridY
,這邊我們將主角 sam
的格子點位置存起來class Weather: GameCharacter, Move {
...
func updateMode() {
switch mode {
case .ATTACK:
self.setTarget(targetX: self.sam!.gridX, targetY: self.sam!.gridY)
default:
break
}
}
func setTarget(targetX: Int, targetY: Int) {
self.targetGridX = targetX
self.targetGridY = targetY
}
}
請先定義以下變數及常數
使用數學公式計算兩點距離
讓我們一起來回憶高中數學,計算兩點間距離的公式
sqrtf
來計算開根號的值新方向位置
及目標點位置
的值 (position)
newX
、newY
的位置依照座標系統加減好targetGridX
、targetGridY
乘以格子寬度計算出來sqrtf
將兩點間的距離 distance
計算出來isTrace
值,代表的是追逐/遠離目標物,這邊預設目前是 true
,我們也先考量未來怪物可能會有逃離行為的移動,依照 isTrace
分別取得較短(追逐)及較長(遠離)路徑的方向class Weather: GameCharacter, Move {
...
func getPathDirection()-> Direction {
let directions: [Direction] = [.LEFT, .RIGHT, .UP, .DOWN]
var pathDirection: Direction = .NONE
var pathDistance: Float = 0.00
var newX = self.node.position.x
var newY = self.node.position.y
for dir in directions {
switch dir {
case .LEFT:
newX = self.node.position.x - CGFloat(self.gridWH)
break
case .RIGHT:
newX = self.node.position.x + CGFloat(self.gridWH)
break
case .UP:
newY = self.node.position.y + CGFloat(self.gridWH)
break
case .DOWN:
newY = self.node.position.y - CGFloat(self.gridWH)
break
case .NONE:
break
}
let lengthA = CGFloat(self.targetGridX * self.gridWH + (self.gridWH/2)) - newX
let lengthB = -CGFloat(self.targetGridY * self.gridWH + (self.gridWH/2)) - newY
let distance = sqrtf(Float(lengthA * lengthA + lengthB * lengthB))
// 取較短的距離
if (self.isTrace && (pathDistance == 0.00 || distance < pathDistance)) {
pathDistance = distance
pathDirection = dir
}
// 取較長的距離
if (!self.isTrace && (pathDistance == 0.00 || distance > pathDistance)) {
pathDistance = distance
pathDirection = dir
}
}
return pathDirection
}
}
回到昨天新加的 startMove
,繼續優化怪物的移動
updateMode
,在每一次的移動時先更新主角的位置getPathDirection
方法取得往目標物的最短方向 bestDirection
newDirection = randomDirection
改為先取看看最短方向,如果取出來的最短方向不包含在可行的方向中時,才改取隨機方向class Weather: GameCharacter, Move {
...
func startMove(direction : Direction) {
let tempDirection: Direction = self.direction
var newDirection: Direction = .NONE
// 更新模式
self.updateMode()
let validDirections: [Direction] = self.getValidDirection()
guard let randomDirection = validDirections.randomElement() else {
return
}
// 取得往目標物的最短方向
let bestDirection = self.getPathDirection()
// 先取得可行的最短方向,否則取隨機方向
// newDirection = randomDirection
newDirection = validDirections.contains(bestDirection) ? bestDirection : randomDirection
...
}
}
我們先暫時將怪物都放在起始點內
並將主角放置在起始點下方
隔著牆的位置有點 bug
可以發現怪物往上跑之後,又馬上往下移動,因為這時候偵測最短路徑應該是向下,但是中間隔著牆壁,造成反覆移動無法走出框框外。直到我們將主角往上移動,怪物才能正常往主角方向前進
我們將 getPathDirection
調整一下,在初始取得方向時,先移除反方向的路線,目的是讓怪物不要一直來回走
class Weather: GameCharacter, Move {
...
func getPathDirection()-> Direction {
// let directions: [Direction] = [.LEFT, .RIGHT, .UP, .DOWN]
var directions: [Direction] = []
...
switch self.direction {
case .LEFT:
directions = [.LEFT, .UP, .DOWN]
case .RIGHT:
directions = [.RIGHT, .UP, .DOWN]
case .UP:
directions = [.LEFT, .RIGHT, .UP]
case .DOWN:
directions = [.LEFT, .RIGHT, .DOWN]
default:
break
}
...
}
}
怪物可以走出框框了,而且可以跟隨著主角移動追擊的目標點
大家是否有發現怪物的追蹤能力太強了呢?
四隻怪物都一起追著主角跑,有點難度太高了。讓怪物有點不同的移動方式,甚至是有點呆呆的,其實也挺有趣味性的
新增兩個格子點:
struct gridMapping {
...
struct crystalCorner {
static let x = 15
static let y = 21
}
struct lakeCorner {
static let x = 14
static let y = 2
}
}
在剛剛已經寫好的 updateMode
方法中,繼續新增設定,依照怪物種類設定攻擊模式時的目標點
self.sam!.gridX
、self.sam!.gridY
gridMapping.crystalCorner.x
、gridMapping.crystalCorner.y
startMove
方法,判斷如果角色為 .LIGHTNING
,就將 newDirection
設定取隨機移動的路線gridMapping.lakeCorner.x
、gridMapping.lakeCorner.y
self.sam!.gridX
、self.sam!.gridY
class Weather: GameCharacter, Move {
...
func updateMode() {
switch mode {
case .ATTACK:
switch self.role {
case .RAIN:
self.setTarget(targetX: self.sam!.gridX, targetY: self.sam!.gridY)
case .STORM:
self.setTarget(targetX: gridMapping.crystalCorner.x, targetY: gridMapping.crystalCorner.y)
case .LIGHTNING:
break
case .SNOW:
let lengthA = self.sam!.node.position.x - self.node.position.x
let lengthB = self.sam!.node.position.y - self.node.position.y
let distance = sqrtf(Float(lengthA * lengthA + lengthB * lengthB))
if distance < Float(10 * self.gridWH) {
self.setTarget(targetX: gridMapping.lakeCorner.x, targetY: gridMapping.lakeCorner.y)
} else {
self.setTarget(targetX: self.sam!.gridX, targetY: self.sam!.gridY)
}
default:
break
}
default:
break
}
}
func startMove(direction : Direction) {
...
// newDirection = validDirections.contains(bestDirection) ? bestDirection : randomDirection
newDirection = self.role == .LIGHTNING ? randomDirection :validDirections.contains(bestDirection) ? bestDirection : randomDirection
...
}
}
成功的讓怪物有不同風格的移動方式了!
能讓玩家比較猜不透他們的移動路徑