从零开始的8-bit迷宫探险【Level 19】这个相遇我等了一辈子了-侦测主角与怪物接触

山姆回想着刚刚看到的雪人怪,还心有余悸。
转了个弯,继续找寻水晶。
「碰!」山姆迎头撞上了白色的物体,带有冰凉的感觉。
「糟了!得赶快逃跑才行。」

今日目标

  • 侦测主角与怪物的接触
  • 播放接触时的动画

PS. 这里是开发 iOS 手机游戏的系列文,如果还没看过之前 剧情 文章的朋友,欢迎先点这边回顾唷!


侦测距离

在 SKScene 类别中的 update 方法,会在游戏的每帧 (per-frame) 被呼叫,我们可以覆写 update 方法,并且将一些想要判断的逻辑放在里面,当符合逻辑时,就可以执行对应的事情

  • 判断主角与所有怪物之间的距离,做出类似接触的感觉
    • 使用 for-in 遍历所有的怪物 (self.weathers)
    • 使用 where 分别写上当两者的 gridXgridY 相等时,另一个轴向的距离相减小於一个格子的宽度 (这边因为图片在之前的章节实作时有放大,所以我们加上了一点数值 6 微调)
    • abs:可以取两者相减後的绝对值
    • 当怪物的模式为 .ATTACK.PLAY 时,且 isSamFallfalse 才能让怪物攻击主角,并且执行 gameStop() 方法。这边设置 isSamFall 的用意是避免连续进入这个逻辑判断,导致重复执行
    • gameStop():将所有 Timer 关闭,并让主角执行跌倒的动画,这边 num 带入 3,repeatAni 带入 false
  • GameScene.swift
class GameScene: SKScene {
    ...
    var isSamFall: Bool = false
    ...
    override func update(_ currentTime: TimeInterval) {
        guard let sam = self.sam else {
            return
        }
        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()
                }
            }
        }
    }
    func gameStop() {
        // 停止 timer
        self.stopTimer()
        
        // 跌倒动画
        if let sam = self.sam {
            sam.playAnimation(imageName: "sam_fall", num: 3, repeatAni: false)
        }
    }
}

接触时的动画序列图

准备三张序列图片,将它们命名为:

  • sam_fall_1
  • sam_fall_2
  • sam_fall_3

https://imgur.com/lVvj6im.png

来看看成果吧

https://imgur.com/WZHP4fj.gif

我们的主角很帅气的滑垒跌倒了!
因为角色们都还是处在移动的状态,接着来调整这个问题

停止移动

角色们都需要有停止的状态,所以我们回到角色 GameCharacter 类别,加上一个参数 isCanMove,预设为 false,用来记录目前是否可以移动
在 Move 协定中,加上设定移动的方法 setCanMove(isCanMove: Bool)

  • GameCharacter.swift
class GameCharacter {
    ...
    var isCanMove: Bool = false
    ...
}
protocol Move {
    func startMove(direction: Direction)
    func endMove()
    func setCanMove(isCanMove: Bool)
}

调整主角 Sam 类别

  • startMove 方法一开始时,先判断目前是否可以移动,如果不行则设定 isMovingfalse ,并且 return,不继续执行後续的移动
  • 实作 setCanMove 方法,改变 isCanMove 的值
  • Sam.swift
class Sam: GameCharacter, Move {
    func startMove(direction: Direction) {
        if !self.isCanMove {
            self.isMoving = false
            return
        }
        ...
    }
    func setCanMove(isCanMove: Bool) {
        self.isCanMove = isCanMove
    }
}

调整怪物 Weather 类别

  • 与主角类别一样,加上可否移动的判断及实作方法
  • Weather.swift
    func startMove(direction : Direction) {
        if !self.isCanMove {
            self.isMoving = false
            return
        }
        ...
    }
    func setCanMove(isCanMove: Bool) {
        self.isCanMove = isCanMove
    }

设定及判断可否移动

  • 在进入游戏时 (didMove)
    • 先将所有怪物设定为可以移动 setCanMove(isCanMove: true),才执行开始移动 startMove(direction: .NONE)
    • 将主角设定为可以移动 setCanMove(isCanMove: true)
  • 游戏停止时 (gameStop)
    • 将主角设定为不能移动,才执行跌倒动画
    • 将所有怪物设定为不能移动
  • 侦测方向按钮 (touchesBegan)
    • 在一开始的地方,先判断主角可否移动,若不行则直接 return
  • GameScene.swift
class GameScene: SKScene {
    override func didMove(to view: SKView) {
        ...
        for weather in self.weathers {
            weather.setCanMove(isCanMove: true)
            weather.startMove(direction: .NONE)
        }
        if let sam = self.sam {
            sam.setCanMove(isCanMove: true)
        }
    }
    func gameStop() {
        ...
        // 停止移动主角 & 跌倒动画
        if let sam = self.sam {
            sam.setCanMove(isCanMove: false)
            sam.playAnimation(imageName: "sam_fall", num: 3, repeatAni: false)
        }
        
        // 停止移动怪物
        for weather in weathers {
            weather.setCanMove(isCanMove: false)
        }
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let sam = self.sam, sam.isCanMove == true else {
            return
        }
        ...
    }
}

看一下最後成果

主角及怪物接触时,所有角色都成功停止移动了

https://imgur.com/j7KUCtz.gif


今日小结

目前我们的游戏画面上,已经有主角及怪物了,但是还少了个重要的东西,就是主角的游戏目的:收集水晶。
明日就会带大家在游戏中加上水晶罗!/images/emoticon/emoticon12.gif


参考来源:
update(_:)
abs


<<:  Day15 简易资料库RealmSwift小实作2

>>:  【在 iOS 开发路上的大小事-Day15】透过 Firebase 来管理使用者 (Sign in with E-mail 篇) Part1

day21 开分支,浅谈kotlin paging3 with flow

注意,我只讲了codelab的50%左右,但对paging3和flow的概念讲完了 通常有codel...

Day11 事件修饰符(2)

上次介绍完前面两个修饰符,今天就来把它学习完吧!!! .stop .prevent .capture...

找LeetCode上简单的题目来撑过30天啦(DAY1)

哈罗,各位好,我本来想写一些,自己想学、有主题的东西,但考量最近工作繁忙,自己又在准备一些考试,所以...

一Ryu大师: REST API

tags: Ryu REST API REST: Representational State Tr...

Ruby on Rails Controller

第 1 步 - 新增 Route 别忘了,使⽤者想要看到你网站上的内容,第⼀步是要问过 Route,...