适应了黑森林的孤寂,山姆开始这趟旅程的目的:找寻水晶。
森林虽然漆黑,但是路还算好走,山姆的脚步也开始轻盈了起来。
「这个路口往左,直直走之後往右...。」
「疑?这里是一小时前就经过的地方,地上还有我做的记号。」山姆面色开始凝重,弥漫一股不祥的预感。
「难道说,这座森林有结界?」
PS. 这里是开发 iOS 手机游戏的系列文,如果还没看过之前
剧情文章的朋友,欢迎先点这边回顾唷!
新增 Direction
列举,把方向名称定义出来,NONE
为无方向,其余为四种方向,包含
enum Direction: String {
case NONE = "none"
case LEFT = "left"
case RIGHT = "right"
case UP = "up"
case DOWN = "down"
}
在 GameCharacter 类别加上移动资讯,包含:
.NONE
false
0.2
(秒)在建构子 (init) 中,将 moveX、moveY 定义好各方向移动一格 (宽度为 gridWH) 需加减的数值
class GameCharacter {
var direction: Direction = .NONE
var isMoving: Bool = false
var moveX: [Direction: CGFloat]
var moveY: [Direction: CGFloat]
var moveInterval = 0.2
init(gridWH: Int, startGridX: Int, startGridY: Int, imageName: String, zPosition: CGFloat, role: Role) {
self.moveX = [
Direction.LEFT: -CGFloat(gridWH),
Direction.RIGHT: CGFloat(gridWH),
Direction.UP: 0,
Direction.DOWN: 0,
Direction.NONE: 0,
]
self.moveY = [
Direction.LEFT: 0,
Direction.RIGHT: 0,
Direction.UP: CGFloat(gridWH),
Direction.DOWN: -CGFloat(gridWH),
Direction.NONE: 0,
]
}
}
新增判断可行方向的方法
getValidDirection
:回传可以走的方向阵列filter
搭配 isWall
方法,找出不是墙的方向新增判断是否为墙壁的方法
true
或 false
gridX
及 gridY
格子点,找出各方向对应位置的值,判断是否为 "w"func getValidDirection()-> [Direction] {
let direction = [
Direction.LEFT,
Direction.RIGHT,
Direction.UP,
Direction.DOWN
]
let validDirection = direction.filter({
self.isWall(dir: $0) == false
})
return validDirection
}
func isWall(dir: Direction) -> Bool {
switch dir {
case .LEFT:
let mapRowArr = Array(map[gridY]);
// 穿梭
if ((gridX == gridMapping.leftPass1.x && gridY == gridMapping.leftPass1.y) || (gridX == gridMapping.leftPass2.x && gridY == gridMapping.leftPass2.y)) {
return false
}
if (gridX - 1 < 0) {
return true
}
return mapRowArr[gridX - 1] == "w"
case .RIGHT:
let mapRowArr = Array(map[gridY]);
// 穿梭
if (gridX == gridMapping.rightPass1.x && gridY == gridMapping.rightPass1.y) {
return false
}
if (gridX + 1 >= mapRowArr.count) {
return true
}
return mapRowArr[gridX + 1] == "w"
case .UP:
if (gridY - 1 < 0) {
return true
}
let mapRowArr = Array(map[gridY - 1]);
if (gridX < 0 || gridX >= mapRowArr.count) {
return true
}
return mapRowArr[gridX] == "w"
case .DOWN:
if (gridY + 1 >= map.count) {
return true
}
let mapRowArr = Array(map[gridY + 1]);
if (gridX < 0 || gridX >= mapRowArr.count) {
return true
}
return mapRowArr[gridX] == "w"
default:
return true
}
}
宣告左右可穿梭的位置点
struct gridMapping {
struct leftPass1 {
static let x = 0
static let y = 1
}
struct leftPass2 {
static let x = 0
static let y = 16
}
struct rightPass1 {
static let x = 16
static let y = 11
}
}
有了判断是否可以行走的方法後,就可以开始来写移动动画了
由於主角跟怪物都会移动,但是移动的方式不同:
因此我们可以新增同样名称,但是不同实作的移动方法,请新增 protocol Move
protocol Move {
func startMove(direction: Direction)
func endMove()
}
请在 Sam 类别中,遵循 Move protocol,并且实作方法
direction
是否为可行的方向direction
,并且将 isMoving
设定为 true
randomElement
随机选取一个。最後改变 node 的 position
SKAction.moveBy
播放移动动画,x
及 y
需带入移动的向量,duration
带入移动间隔秒数。并透过 .run
执行动画,当动画完成时,执行 endMove
方法direction
设定为 Direction.NONE
,并且将 isMoving
设定为 false
,不再播放移动动画,停止移动startMove
方法,持续播放下一格的移动动画class Sam: GameCharacter, Move {
...
func startMove(direction: Direction) {
let validDirection = self.getValidDirection()
if (validDirection.contains(direction)) {
self.direction = direction
self.isMoving = true
// 左右穿梭
if ((gridX == gridMapping.leftPass1.x && gridY == gridMapping.leftPass1.y && direction == .LEFT) || (gridX == gridMapping.leftPass2.x && gridY == gridMapping.leftPass2.y && direction == .LEFT) || (gridX == gridMapping.rightPass1.x && gridY == gridMapping.rightPass1.y && direction == .RIGHT)) {
self.gridX = direction == .LEFT ? gridMapping.rightPass1.x + 1 : gridMapping.leftPass1.x - 1
self.gridY = direction == .LEFT ? gridMapping.rightPass1.y : [gridMapping.leftPass1.y, gridMapping.leftPass2.y].randomElement()!
self.node.position = CGPoint(x: (gridX * gridWH) + (gridWH/2), y: -gridY * gridWH - (gridWH/2))
}
// 播放移动动画
let animation = SKAction.moveBy(x: self.moveX[direction]!, y: self.moveY[direction]!, duration: self.moveInterval)
self.node.run(animation, completion: endMove)
// 设定格子
self.setGridXY(direction: self.direction)
} else {
self.direction = Direction.NONE
self.isMoving = false
}
}
func endMove() {
self.startMove(direction: self.direction)
}
}
设定格子点的 x、y,可以对照地图阵列 map,方便我们纪录角色移动後的位置
func setGridXY(direction: Direction) {
switch self.direction {
case .LEFT:
self.gridX -= 1;
case .RIGHT:
self.gridX += 1;
case .UP:
self.gridY -= 1;
case .DOWN:
self.gridY += 1;
case .NONE:
break
}
}
我们在主角的类别中,先写上点击方向按钮後要触发的方法 setDirection
由於方向按钮是随时都可以点击的,所以先判断点击的方向是否为可行的路,如果不可行的话就忽略它,在这边我们印出 "此路不通!!"。如果是可行的,就将方向存到 self.direction
。
在游戏开始时,角色是静止的,直到玩家按下方向按钮才会开始朝着某方向持续前进,因此若判断 isMoving
是 false
时,我们就呼叫开始移动的方法 startMove
,如果已经正在移动了,就只需要设定新的方向值,移动动画会自动接续朝着新的方向移动。
func setDirection(direction: Direction) {
let validDirection = self.getValidDirection()
if (validDirection.contains(direction)) {
self.direction = direction
if (!self.isMoving) {
self.startMove(direction: direction)
}
} else {
print("此路不通!!")
}
}
这边来介绍直接在 GameScene.sks
上加 node,由程序码取得 node 资讯的方式
请点击右上角的 + ,拖拉 Color Sprite 到 Scene 中
新增一个 btns node,里头新增四个按钮 node,可以依照下图设定属性
回到程序码的地方,将按钮位置置底置中
applySafeArea
方法中调整位置childNode
找到场景中的按钮外层 node:btns
bottomSafeArea
校正回来class GameScene: SKScene {
...
func applySafeArea() {
...
if let btnNode = self.childNode(withName: "//btns") as? SKSpriteNode {
btnNode.position.x = self.size.width / 2
btnNode.position.y = -self.size.height + bottomSafeArea + 75
}
}
}
接着写上侦测点击按钮的方法
childNode
找到场景中的四个方向按钮setDirection
class GameScene: SKScene {
...
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let sam = self.sam else {
return
}
let btnLeft = self.childNode(withName: "//left") as? SKSpriteNode
let btnRight = self.childNode(withName: "//right") as? SKSpriteNode
let btnUp = self.childNode(withName: "//up") as? SKSpriteNode
let btnDown = self.childNode(withName: "//down") as? SKSpriteNode
for touch in (touches) {
let location = touch.location(in: self)
if self.atPoint(location) == btnLeft {
sam.setDirection(direction: Direction.LEFT)
}
if self.atPoint(location) == btnRight {
sam.setDirection(direction: Direction.RIGHT)
}
if self.atPoint(location) == btnUp {
sam.setDirection(direction: Direction.UP)
}
if self.atPoint(location) == btnDown {
sam.setDirection(direction: Direction.DOWN)
}
}
}
}
我们来看一下目前的成果吧!
主角可以按照我们点的按钮移动罗,而且遇到墙壁也能成功停下来,也能穿梭到地图的另一边!
但是看起来还少了点什麽,角色是用平移的方式走路,我们来帮他换个方向吧
目前我们已经有正面的序列图,我们继续把其他方向的图补完
将图片命名好:
在角色类别中写个共用的播放图片序列动画方法:
true
sequences
,存入 SKTexture
类别的图片序列repeatForever
func playAnimation(imageName: String, num: Int, repeatAni: Bool = true) {
var sequences: [SKTexture] = []
for index in 1...num {
let sequence = SKTexture(imageNamed: imageName + "_" + String(index))
sequences.append(sequence)
}
let ani = SKAction.animate(with: sequences, timePerFrame: 0.4)
if repeatAni {
let aniRepeat = SKAction.repeatForever(ani)
self.node.run(aniRepeat, withKey: "sequence")
return
}
self.node.run(ani, withKey: "sequence")
}
在刚刚的按钮点击方法中,呼叫 playAnimation
,并带入对应方向的图片名称
for touch in (touches) {
let location = touch.location(in: self)
if self.atPoint(location) == btnLeft {
sam.setDirection(direction: Direction.LEFT)
sam.playAnimation(imageName: "sam_left", num: 2)
}
if self.atPoint(location) == btnRight {
sam.setDirection(direction: Direction.RIGHT)
sam.playAnimation(imageName: "sam_right", num: 2)
}
if self.atPoint(location) == btnUp {
sam.setDirection(direction: Direction.UP)
sam.playAnimation(imageName: "sam_up", num: 2)
}
if self.atPoint(location) == btnDown {
sam.setDirection(direction: Direction.DOWN)
sam.playAnimation(imageName: "sam_down", num: 2)
}
}
来看看成果吧!
优惠码SUMMER30 优惠时间:只到2021/9/5 折扣内容:首2个月7折(适用於所有方案) ☞...
在下还只是一位新手请大家鞭小力点 如果要执行用Visual Studio的C#或用python写出的...
We are making your quest for VIP coats simpler by ...
前文提到页是Innodb的基本存取单位,一般为16kb,Innodb为了实现功能其实设计了许多不同类...
第二十七天来讲到第六题练习题 这题题目有点冗长,害得我当时都有点懒得看了 题目大意是:要写一个程序自...