从零开始的8-bit迷宫探险【Level 15】迷人的反派角色-制作怪物

正当山姆思考结界问题的同时,啪嗒!啪嗒!雨落了下来。
「下雨了!」山姆赶紧找寻遮蔽物,跑到了一棵大树下。
「呼,好险。」山姆喘了一口气,还好背包没有湿掉。
山姆蹲下系紧因奔跑而松脱的鞋带,抬起头时,出现了一双眼睛紧盯着。
四目交对之下,山姆快速扫遍脑中的资料库...
「那是...雪人?这个季节里居然有雪人!?」

今日目标

  • 新增怪物类别,继承角色类别 (GameCharacter)
  • 怪物的动画
  • 新增四个怪物在游戏画面中

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


怪物角色设计

依照我们的游戏设计规划,怪物共有四种:

  • 坏天气1号:下雨 (Rain)
  • 坏天气2号:暴风雨 (Storm)
  • 坏天气3号:闪电 (Lightning)
  • 坏天气4号:雪人 (Snow)

以下先设计好怪物的样子,每种怪物都先分别设计两张动画序列图,我们先制作一种方向 (往下走的正面),并将图片拖移进专案中

  • 下雨 (Rain):

    • 档名 rain_down_1、rain_down_2
      https://imgur.com/NpvXjs8.pnghttps://imgur.com/B4k4twc.png
  • 暴风雨 (Storm):

    • 档名 storm_down_1、storm_down_2
      https://imgur.com/q7LQt5A.pnghttps://imgur.com/AofEeDM.png
  • 闪电 (Lightning):

    • 档名 lightning_down_1、lightning_down_2
      https://imgur.com/G9GOBYt.pnghttps://imgur.com/9hBissO.png
  • 雪人 (Snow):

    • 档名 snow_down_1、snow_down_2
      https://imgur.com/4Ljooro.pnghttps://imgur.com/nc6HXBS.png

怪物的设定

先前已经定义过主角的一些设定值,我们先将怪物的设定值也加进专案中

怪物种类

在之前主角篇新增的 Role 列举,再多加入四种怪物名称 RAINSTORMLIGHTNINGSNOW

  • GameCharacter.swift
enum Role: String {
    case NONE = "none"
    case SAM = "sam"
    case RAIN = "rain"
    case STORM = "storm"
    case LIGHTNING = "lightning"
    case SNOW = "snow"
}

怪物起始位置

将怪物在迷宫中的起始位置定义在之前宣告的 gridMapping 结构 (struct) 中,方便一起管理

  • GameScene.swift
struct gridMapping {
    ...
    struct rainStart {
        static let x = 7
        static let y = 8
    }
    struct stormStart {
        static let x = 8
        static let y = 10
    }
    struct lightningStart {
        static let x = 6
        static let y = 10
    }
    struct snowStart {
        static let x = 9
        static let y = 8
    }
}

新增怪物类别

接着,请新增一个 swift 档案,命名为 Weather.swift

  • 点选新增档案
    https://imgur.com/DyRgPfA.png

  • 选择 Swift File -> Next
    https://imgur.com/IJukGWI.png

  • 将档案命名为 Weather,点击 Create 按钮,完成 .swift 档的新增

引入 SpriteKit

请先 import SpriteKit

  • Weather.swift
import SpriteKit

怪物的移动模式种类

在这边我们先定义好怪物将会进行的移动模式种类,将会在未来的章节实做移动的方式

  • ATTACK:追击主角
  • ESCAPE:逃离主角
  • PLAY:聚集到湖边玩耍
  • REBIRTH:回到出生点重生
  • Weather.swift
enum Mode {
    case ATTACK
    case ESCAPE
    case PLAY
    case REBIRTH
}

类别的内容

将怪物类别 (Weather) 继承角色类别 (GameCharacter)
接着加上怪物专有的一些属性:

  • mode:怪物的移动模式,预设给 .ATTACK
  • isTrace:纪录目前正在追踪目标或是远离目标,预设给 true
  • targetGridX:目标物的格子点 x
  • targetGridY:目标物的格子点 y
  • sam:主角,追击时需要主角的位置资料

接着在建构子 (init) 写上初始化时需带入的参数:

  • gridWH:一格的宽度
  • startGridX:角色初始格子的位置 x
  • startGridY:角色初始格子的位置 y
  • role:角色种类
  • sam:主角

在 super.init 的时候带入的参数:

  • imageName 带入 "\(role.rawValue)_down",对应到前面加入的图片档名,例如 rain_down。在 GameCharacter 类别中,先前已经在 init 写好播放动画序列图的程序码,带入图档名就可以产生序列动画了
  • zPosition 带入 ZPosition.WEATHER,在先前的章节已经加好的怪物层级

并且将 sam 主角的资料存起来

  • Weather.swift
class Weather: GameCharacter {
    var mode: Mode = .ATTACK
    var isTrace: Bool = true
    var targetGridX = 0
    var targetGridY = 0
    var sam: Sam?
    
    init(gridWH: Int, startGridX: Int, startGridY: Int, role: Role, sam: Sam) {
        super.init(gridWH: gridWH, startGridX: startGridX, startGridY: startGridY, imageName: "\(role.rawValue)_down", zPosition: CGFloat(ZPosition.WEATHER), role: role)
        self.sam = sam
    }
}

新增怪物到游戏中

回到 GameScene,我们新增怪物的实体,将怪物新增到游戏中

  • 在这边先宣告阵列 weathers,准备储存所有的怪物
  • didMove 里分别新增四个怪物:rainstormlightningsnow,并将它们的 node 加到 mapNode 里面,以及将实体加进 weathers 阵列中
  • GameScene.swift
class GameScene: SKScene {
    ...
    var weathers: [Weather] = []
    
    override func didMove(to view: SKView) {
        ...
        let rain = Weather(gridWH: self.gridWH, startGridX: gridMapping.rainStart.x, startGridY: gridMapping.rainStart.y, role: .RAIN, sam: self.sam!)
        self.mapNode!.addChild(rain.node)
        self.weathers.append(rain)
        
        let storm = Weather(gridWH: self.gridWH, startGridX: gridMapping.snowStart.x, startGridY: gridMapping.snowStart.y, role: .SNOW, sam: self.sam!)
        self.mapNode!.addChild(storm.node)
        self.weathers.append(storm)

        let lightning = Weather(gridWH: self.gridWH, startGridX: gridMapping.lightningStart.x, startGridY: gridMapping.lightningStart.y, role: .LIGHTNING, sam: self.sam!)
        self.mapNode!.addChild(lightning.node)
        self.weathers.append(lightning)

        let snow = Weather(gridWH: self.gridWH, startGridX: gridMapping.stormStart.x, startGridY: gridMapping.stormStart.y, role: .STORM, sam: self.sam!)
        self.mapNode!.addChild(snow.node)
        self.weathers.append(snow)
    }
}

执行成果

来看一下执行的成果吧!
https://imgur.com/uudL2ZW.gif


今日小结

目前我们有主角 (Sam) 及怪物 (Weather) 两种类别了,同样都继承角色 (GameCharacter) 父类别,而透过继承类别的好处是省略了一些重复的程序码,像是初始化的地方,已经可以不用再多写一次播放序列动画的程序码,并且也能依照特殊需求,再多加入特别的参数,使我们能更快速建立不同种类的角色。
聪明的各位应该也猜到了,接下来一样可以遵循前面建立好的移动协定 Move,明日就来带着大家一起实作让怪物动起来吧!


<<:  知识管理附加健康管理

>>:  [Day23] NLP会用到的模型(六)-transformer架构

CallStack

由於JavaScript是单线程的语言,所以从上而下设计就很重要,若有点困难可以先去看Functio...

Day27 D3js 动画事件小技巧

D3js 动画事件小技巧 用途 在d3世界中,如果想使用起来不是透过事件驱动或是外部驱动,而是想要产...

那些被忽略但很好用的 Web API / RequestAnimationFrame

别再用 setTimeOut、setInterval 写动画啦! 如果你有用 js 写过动画,那通...

Day29 javascript RegExp介绍

今天要来看的是JavaScript RegExp 对象,因为刚好最近做表单要用到,因此就来顺便做点笔...

Day11:11 - 商品服务(2) - 前端 - 总商品资料显示

שלום,我是Charlie! 在Day10当中我们完成了後端的商品资料API,在今天我们将完成前端...