远方有个一闪一闪的东西,吸引了山姆的目光。
「这颗水晶...特别的大颗!」山姆跑了过去。
碰触到魔幻水晶的那一刹那,彷佛得到了全宇宙的力量。
这是攻守互换的一刻!山姆的反击开始了!
PS. 这里是开发 iOS 手机游戏的系列文,如果还没看过之前
剧情文章的朋友,欢迎先点这边回顾唷!
我们新增魔幻水晶类别,让它继承收集物类别
请新增一个 swift 档案
点选新增档案
选择 Swift File -> Next
将档案命名为 MagicalCrystal,点击 Create 按钮,完成 .swift 档的新增
请先 import SpriteKit
import SpriteKit
这边做个让魔幻水晶可以闪烁的效果,我们新增 playAnimation
方法
SKAction.fadeAlpha
来做闪烁效果,to
带入 alpha 值 (不透明度),duration
带入持续时间SKAction.sequence
带入 SKAction 的阵列SKAction.repeatForever
重复播放这个序列动作run
方法播放动画class MagicalCrystal: Collection {
func playAnimation() {
let ani1 = SKAction.fadeAlpha(to: 0, duration: 0.6)
let ani2 = SKAction.fadeAlpha(to: 1, duration: 0.3)
let aniAlpha = SKAction.sequence([ani1, ani2])
let aniRepeat = SKAction.repeatForever(aniAlpha)
self.node.run(aniRepeat)
}
}
在先前的章节已经设定过地图上要画的图片代号,想复习的朋友可以点这边
*
代号代表这个位置要画上魔幻水晶let mapDraw = [
"ccccccccpccccccci",
" .....e*......b",
"aam.1ji.s.11.zy.b",
" d.3gh....1.wx.b",
"jcl.....ji.1....b",
"d*...11.gh...rt.b",
"d.11.nm....2....b",
"d..1.kl.22.1.naah",
"ot... .b ",
"d..1.jcu vci.b ",
"d.12.d b.kccc",
"d+...gaaaaah. ",
"gaam. .11.n",
" d.rt.1#.q....b",
" d....21.e.ji.b",
"cccl.ji....e.gh.b",
" .gh.13.s....b",
"aaam....1....21.b",
" d...23.rt.1..b",
" d.1.........3b",
"jccl.1.rft.3.1.1b",
"d*.............*b",
"gaaaaaaaaaaaaaaah",
]
请准备好图片,并且拖拉进专案中
magicCrystals
,准备将所有画面上的魔幻水晶存在这边drawMap
方法里,再加上 "*" 的 casemapNode
里加入魔幻水晶的 node
magicCrystals
阵列中class GameScene: SKScene {
...
var magicCrystals: [Collection] = []
...
func drawMap() {
for i in 0..<gridYCount {
let mapRowArr = Array(mapDraw[i]);
for j in 0..<gridXCount {
let mapKeys = wallMapping.keys
switch mapRowArr[j] {
...
case "*":
let magicalCrystal = MagicalCrystal(gridWH: self.gridWH, gridX: j, gridY: i, imageName: "magical-crystal")
self.mapNode!.addChild(magicalCrystal.node)
self.magicCrystals.append(magicalCrystal)
magicalCrystal.playAnimation()
default:
break
}
}
}
}
}
update
方法里,再加上主角跟魔幻水晶之间的位置判断,当主角跟魔幻水晶的格子位置一样时,并且还没有被收集时,就将魔幻水晶设定成被收集了 setGotten(isGotten: true)
class GameScene: SKScene {
override func update(_ currentTime: TimeInterval) {
...
for magicCrystal in self.magicCrystals where !magicCrystal.isGotten && magicCrystal.gridX == sam.gridX && magicCrystal.gridY == sam.gridY {
magicCrystal.setGotten(isGotten: true)
}
}
}
setMode(mode: .ESCAPE)
magicTimer
,先判断如果 magicTimer
不为 nil 时,关闭上一个 magicTimer
,这边的目的是防止 Timer 还没结束时又再次启动 TimereacapeToAttackModeAction
class GameScene: SKScene {
...
var magicTimer: Timer? = nil
...
override func update(_ currentTime: TimeInterval) {
...
for magicCrystal in self.magicCrystals where !magicCrystal.isGotten && magicCrystal.gridX == sam.gridX && magicCrystal.gridY == sam.gridY {
magicCrystal.setGotten(isGotten: true)
for weather in self.weathers {
weather.setMode(mode: .ESCAPE)
}
if let magicTimer = self.magicTimer {
magicTimer.invalidate()
}
// 20秒後回复
self.magicTimer = Timer.scheduledTimer(timeInterval: 20, target: self, selector: #selector(eacapeToAttackModeAction), userInfo: nil, repeats: false)
}
}
@objc func eacapeToAttackModeAction() {
self.magicTimer = nil
for weather in self.weathers {
if weather.mode == .ESCAPE {
weather.setMode(mode: .ATTACK)
}
}
}
}
变成逃逸模式後的怪物,会呈现另一种样貌,作为区别
getImage
中加入 .ESCAPE
的 case 了,档名为 cloud
enum Mode {
case ATTACK
case ESCAPE
case PLAY
case REBIRTH
func getImage(role: Role) -> String {
switch self {
...
case .ESCAPE:
return "cloud"
case .REBIRTH:
return "water"
}
}
}
updateMode
方法里,加上 .ESCAPE
case
isTrace
值改为 false
,让怪物侦测的路径是往取得离主角较远的方向前进moveInterval
,将速度调稍快一点class Weather: GameCharacter, Move {
func updateMode() {
switch mode {
case .ATTACK:
...
self.setPathMode(isTrace: true)
case .PLAY:
...
self.setPathMode(isTrace: true)
case .ESCAPE:
self.setTarget(targetX: self.sam!.gridX, targetY: self.sam!.gridY)
self.setPathMode(isTrace: false)
self.moveInterval = 0.25
...
}
}
func setPathMode(isTrace: Bool) {
self.isTrace = isTrace
}
}
可以看到当吃到魔幻水晶後,怪物都变成白云了,并且往主角的反方向移动。
这时候当怪物与主角碰触时,怪物就不能攻击主角了。
主角与怪物接触
判断的地方,再加上当前模式为 .ESCAPE
的判断,让怪物变成重生的模式 setMode(mode: .REBIRTH)
class GameScene: SKScene {
...
override func update(_ currentTime: TimeInterval) {
...
for weather in self.weathers where weather.gridX == sam.gridX && abs(weather.node.position.y - sam.node.position.y) <= CGFloat(self.gridWH + 6) || weather.gridY == sam.gridY && abs(weather.node.position.x - sam.node.position.x) <= CGFloat(self.gridWH + 6) {
if weather.mode == .ATTACK || weather.mode == .PLAY {
if !self.isSamFall {
self.isSamFall = true
self.gameStop()
}
} else if weather.mode == .ESCAPE {
// 让怪物重生
weather.setMode(mode: .REBIRTH)
}
}
}
}
变成重生模式後的怪物,会呈现水滴的样貌
weatherHome
isTrace
为 true
moveInterval
,将移动速度调快class Weather: GameCharacter, Move {
...
func updateMode() {
switch mode {
...
case .REBIRTH:
self.setTarget(targetX: gridMapping.weatherHome.x, targetY: gridMapping.weatherHome.y)
self.setPathMode(isTrace: true)
self.moveInterval = 0.2
}
}
}
struct gridMapping {
...
struct weatherHome {
static let x = 8
static let y = 10
}
}
为了让怪物能更快的回到出生点,我们分别在出生点的左右入口处,以及其上方入口处新增位置设定
struct gridMapping {
...
struct weatherHomeEntryLeft {
static let x = 4
static let y = 8
}
struct weatherHomeEntryRight {
static let x = 12
static let y = 8
}
struct weatherHomeEntry {
static let x = 8
static let y = 8
}
}
startMove
方法
.REBIRTH
,以及当下的格子位置在 weatherHomeEntryLeft
、weatherHomeEntryRight
、weatherHomeEntry
则强制让怪物分别往 .RIGHT
、.LEFT
、.DOWN
的方向移动,让怪物顺利回到出生点weatherHomeEntry
的位置时,则让怪物只能左右移动,无法回到出生点class Weather: GameCharacter, Move {
...
func startMove(direction : Direction) {
...
if !isBack || !validDirections.contains(self.direction) {
self.direction = newDirection
}
if self.mode == .REBIRTH {
if gridX == gridMapping.weatherHomeEntryLeft.x && gridY == gridMapping.weatherHomeEntryLeft.y {
self.direction = .RIGHT
}
if gridX == gridMapping.weatherHomeEntryRight.x && gridY == gridMapping.weatherHomeEntryRight.y {
self.direction = .LEFT
}
if gridX == gridMapping.weatherHomeEntry.x && gridY == gridMapping.weatherHomeEntry.y {
self.direction = .DOWN
}
} else { // 其余时间不能回家
if gridX == gridMapping.weatherHomeEntry.x && gridY == gridMapping.weatherHomeEntry.y {
self.direction = [Direction.LEFT, Direction.RIGHT].randomElement()!
}
}
...
}
}
.ATTACK
,再次回到攻击模式class GameScene: SKScene {
...
override func update(_ currentTime: TimeInterval) {
...
for weather in self.weathers where weather.mode == .REBIRTH && weather.gridX == gridMapping.weatherHome.x && weather.gridY == gridMapping.weatherHome.y {
weather.setMode(mode: .ATTACK)
}
}
}
主角可以反击怪物了!怪物会变成水滴往出生点的位置移动,一但回到出生点,又会再次变回攻击模式
目前怪物所有的样貌和模式已经有了一个基本循环
明日会在游戏中再加上一些特殊道具的设定,增添游戏性
<<: Day 15 - 将 COMPANY 後台储存资料提取後,送至 Certificat 前台渲染画面 - 相簿资料渲染 - ASP.NET Web Forms C#
>>: D19 - 用 Swift 和公开资讯,打造投资理财的 Apps { 移动平均线(MA线)实作.2 }
接着来讲讲泛型的部分.... 简单来说泛型就是传入值、传回值不固定的情况下这时候就可以使用泛型......
关於网站 现在网站早已不像以前只是个一页式的静态网站,可能会多很多功能,例如:留言、回覆、公布栏等等...
54. Spiral Matrix https://leetcode.com/problems/sp...
还记得做专案时,为了要与非IT专业的高层人士沟通,我们常常需要画业务流程图, 从开始、结束、程序、路...