从零开始的8-bit迷宫探险【Level 25】今天又是崭新的一天,回到原点

山姆再次勇闯黑森林,但是这次他大意了!
在旁埋伏的 Storm 跟 Lightning 趁山姆一不注意,使出了闪电暴风雨。
淋湿的山姆倒在地上。
幸好,这里是从零开始的游戏世界,山姆不只一条命。
於是山姆又重新站了起来,再次从起点出发。

今日目标

  • 新增主角的生命值,并显示在游戏画面上
  • 主角被怪物攻击後,生命值减 1,主角与怪物都回到原点
  • 建立游戏的流程,让游戏可以不断的重新开始
  • 让游戏开始时有个缓冲时间,再开始游戏

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


加入生命值机制

目前主角一被怪物攻击後,就会直接游戏结束,但是这样的节奏太快了,因此我们为主角新增生命值的机制,让主角除了一开始的生命外,还有额外三条命 (共 4 条命)。

  • 新增 samLife 纪录还有几条生命
  • 新增 lifeNodelifeIconNodelifeLabel 节点来显示主角的图示及剩下的生命值
    • lifeNode:外层的 node,设定与萤幕同宽,高 30,加到游戏场景中
    • lifeIconNode:图示的 node,这边放上主角的图片,设定宽高及位置,加到 lifeNode
    • lifeLabel:显示主角剩余生命值,文字设定为 "X \(self.samLife)",设定文字的颜色、尺寸、字体、位置,让他垂直置中,水平置左,并加到 lifeNode
  • 调整 applySafeArea 方法,校正 position,让生命值的外层节点 lifeNode 的位置在地图节点下方
  • GameScene.swift
class GameScene: SKScene {
    ...
    var samLife: Int = 3
    var lifeNode: SKSpriteNode?
    var lifeIconNode: SKSpriteNode?
    var lifeLabel: SKLabelNode?
    
    override func didMove(to view: SKView) {
        ...
        self.lifeNode = SKSpriteNode(color: .clear, size: CGSize(width: CGFloat(self.size.width), height: CGFloat(30)))
        self.lifeIconNode = SKSpriteNode(imageNamed: "sam_down_1")
        self.lifeLabel = SKLabelNode(text: "X \(self.samLife)")
        if let lifeNode = self.lifeNode, let lifeIconNode = self.lifeIconNode, let lifeLabel = self.lifeLabel {
            lifeNode.anchorPoint = CGPoint(x: 0, y: 0.5)
            self.addChild(lifeNode)
            
            lifeIconNode.anchorPoint = CGPoint(x: 0, y: 0.5)
            lifeIconNode.size.width = CGFloat(25)
            lifeIconNode.size.height = CGFloat(25)
            lifeIconNode.position = CGPoint(x: 5, y: 0)
            lifeNode.addChild(lifeIconNode)

            lifeLabel.fontColor = UIColor.white
            lifeLabel.fontSize = CGFloat(22)
            lifeLabel.fontName = "Copperplate"
            lifeLabel.position = CGPoint(x: 35, y: 0)
            lifeLabel.verticalAlignmentMode = .center
            lifeLabel.horizontalAlignmentMode = .left
            lifeNode.addChild(lifeLabel)
        }
    }
    
    func applySafeArea() {
        ...
        if let mapNode = self.mapNode, let scoreNode = self.scoreNode, let lifeNode = self.lifeNode {
            mapNode.position = CGPoint(x: 0, y: -self.topSafeArea - scoreNode.size.height)
            scoreNode.position =  CGPoint(x: 0 ,y: -self.topSafeArea - scoreNode.size.height/2)
            lifeNode.position = CGPoint(x: 0, y: -self.topSafeArea - scoreNode.size.height - mapNode.size.height - 15)
        }
    }
}

执行结果

游戏中已经出现显示生命值的画面罗!
https://imgur.com/xpDana5.gif


游戏重新开始流程

有了计算剩余生命值的变数後,可以依照剩余的生命数量,让游戏重新开始或是真的游戏结束。今天我们会先带大家实作游戏重新开始的部分,游戏结束明天会再说明
在这边我们可以建立游戏重新开始的流程,需要做的事情有:

  • 重设设定
    • 主角减一条生命、跌倒状态回到预设
    • 主角及怪物都设定回原本的位置
  • 游戏开始
    • 主角及怪物状态设定为可以移动
    • 怪物开始移动
    • 设定随机启动玩耍模式的 Timer

重设设定

  • 找到之前写好的 gameStop 方法 (在主角被怪物攻击时会触发),加上以下程序码:
    • samLife生命值减 1
    • 判断生命值
      • 若以无剩余生命,则游戏结束,我们先印出 log,後续再加上动作
      • 若还有剩余生命,则设定 3 秒後将游戏重设 gameReset
  • 游戏重新设定 gameReset
    • 更新画面上角色的生命值
    • 呼叫 resetPosition 方法,将主角及怪物设定回到游戏初始时的位置
    • 将怪物设定回攻击模式 .ATTACK
    • 将主角跌倒状态重置 self.isSamFall = false
  • GameScene.swift
class GameScene: SKScene {
    func gameStop() {
        ...
        self.samLife -= 1
        if self.samLife < 0 {
            print("游戏结束")
        } else {
            Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(gameReset), userInfo: nil, repeats: false)
        }
    }
    @objc func gameReset() {
        self.lifeLabel?.text = "X \(self.samLife)"
        for weather in self.weathers {
            weather.resetPosition()
            weather.setMode(mode: .ATTACK)
        }
        if let sam = self.sam {
            sam.resetPosition()
        }
        self.isSamFall = false
    }
}

角色重设位置

在共用的角色类别中新增 resetPosition 方法

  • gridXgridY 设定回初始格子点
  • 更新 position
  • 播放对应的角色动画
  • GameCharacter.swift
class GameCharacter {
    ...
    func resetPosition() {
        self.gridX = startGridX
        self.gridY = startGridY
        self.node.position = CGPoint(x: self.gridWH * self.startGridX + (self.gridWH/2), y: -self.gridWH * self.startGridY - (self.gridWH/2))
        self.playAnimation(imageName: self.imageName, num: 2)
    }
}

执行结果

主角被攻击 3 秒後,角色们都回到原本的位置,主角生命值减 1
https://imgur.com/rzlKp6Z.gif

游戏开始

考量到游戏的流程是会不断循环的,我们可以新增一个共用的游戏开始方法,在一进游戏时、以及在主角减一条生命後,都可以呼叫

  • 新增 gameStart 方法,让游戏开始後有一段时间的缓冲,让玩家可以看一下地图,才游戏开始。请设定 Timer,5 秒後执行游戏开始动作 gameStartAction
  • 将原本写在 didMove 的开始游戏後的动作移到 gameStartAction,包含设定角色们可以移动、让怪物开始移动,以及产生随机时间後变成玩耍模式的 Timer
  • GameScene.swift
class GameScene: SKScene {
    ...
    func gameStart() {
        Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(gameStartAction), userInfo: nil, repeats: false)
    }
    @objc func gameStartAction() {
        for weather in self.weathers {
            weather.setCanMove(isCanMove: true)
            weather.startMove(direction: .NONE)
        }
        if let sam = self.sam {
            sam.setCanMove(isCanMove: true)
        }
        let randomTime = Int.random(in: 10...50)
        self.randomTimer = Timer.scheduledTimer(timeInterval: TimeInterval(randomTime), target: self, selector: #selector(attackToPlayModeAction), userInfo: nil, repeats: false)
    }
}

呼叫游戏开始方法

  • 在进入游戏後,呼叫 gameStart 方法
  • 角色被攻击後,在 gameRestart 里呼叫 gameStart
  • GameScene.swift
class GameScene: SKScene {
    ...
    override func didMove(to view: SKView) {
        ...
        self.gameStart()
    }
    @objc func gameRestart() {
        ...
        self.gameStart()
    }
}

执行结果

游戏成功加上生命值机制了!在生命值没有归零前,可以不断重新开始游戏,之前收集的水晶跟分数也都还会保留
https://imgur.com/hqjtipx.gif


今日小结

目前已经加上重新开始游戏的流程了,明日会继续将游戏结束後的流程加上
我们预计在游戏结束时,会切换到另外一个场景,并且显示玩家这场的游戏得分,再加上重新开始游戏的按钮
我们明日见罗~/images/emoticon/emoticon13.gif


<<:  Day 19 - 将 NEWS 後台储存资料提取後,送至前台渲染画面 (上) - News List Page CTE 暂存表应用 - ASP.NET Web Forms C#

>>:  Day18 - 使用阵列实作随机回覆

Day 17 - Applicative

Introduction Type Signature of :: Applicative f =&...

使用 TorchServe 部署 Model

TorchServe TorchServe 是 PyTorch 提供给开发者部署 models 的工...

# Day30--Push?Pull?跟GitHub好好沟通沟通

Yo!这应该就是我的最後一篇的铁人文啦!撒花 其实Git真的很多东西可以说,一下子要讲一些细节的操作...

D3JsDay20笔画面量彩色图涂色 彩亮面画笔—地理面量图(上)

面量图介绍 面量图又称分层设色图、区域密度图(Choropleth map),高中地理课本的说明是在...

Day 4 基本型别 - part 1

今天要介绍 TypeScript 的基本型别,TypeScript 跟 JavaScript 一样拥...