黑森林的样貌正如其名,不管白天或黑夜,一但走进了森林里就伸手不见五指...
长老说:「少年,你确定要去吗?」
山姆坚定地说:「是的。」
「因为,这是身为探险家的必经之路啊!」
山姆背对着长老,挥了挥手,阳光洒落在他的帽檐上,特别耀眼。
首先,我们先创建一个游戏模板的专案
选择 Game 模板
给专案一个很酷的名称
选择好要创建的位置,点击 Create 就创建完成罗!
清除 GameScene.sks 中的 helloLabel
删除 Action.sks
GameScene.swift
把不需要的程序码删除,只留下这些:
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
}
}
view.showsFPS = false
view.showsNodeCount = false
我们的目标是要做出可以让角色在迷宫内移动的地图,因此采用格子的方式来拼出迷宫,每个格子可以代表墙壁或是路,墙壁会放上不同种类的墙壁贴图,而路则可以让角色移动。
首先先决定要绘制的迷宫格子尺寸,我们将绘制:宽:17 格、高:23 格的地图。
先把地图的样子写出来吧!
使用阵列,画出 17 x 23 样子的地图,并且用符号表示:
(今天先处理画出墙壁的部分,其他後续的单元会说明)
除了 w (墙壁) 以外,都可以让角色行走
而选择使用一维阵列,主要是想让程序码看起来跟迷宫比较相近、简洁
let map = [ "wwwwwwwwwwwwwwwww",
" .....w*......w",
"www.www.w.ww.ww.w",
" w.www....w.ww.w",
"www.....ww.w....w",
"w*...ww.ww...ww.w",
"w.ww.ww....w....w",
"w..w.ww.ww.w.wwww",
"ww... .w ",
"w..w.www www.w ",
"w.ww.w w.wwww",
"w+...wwwwwww. ",
"wwww. .ww.w",
" w.ww.w#.w....w",
" w....ww.w.ww.w",
"wwww.ww....w.ww.w",
" .ww.ww.w....w",
"wwww....w....ww.w",
" w...ww.ww.w..w",
" w.w.........ww",
"wwww.w.www.w.w.ww",
"w*.............*w",
"wwwwwwwwwwwwwwwww",
]
用表格表示可能会清楚些!以下是地图内所有的格子点
横的列,未来会定义 gridX 来表示 (范围0~16)
直的栏,未来会定义 gridY 来表示 (范围0~22)
透过 gridX 及 gridY 来记录角色或当前物体的所在位置
- | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
00 | |||||||||||||||||
01 | |||||||||||||||||
02 | |||||||||||||||||
03 | |||||||||||||||||
04 | |||||||||||||||||
05 | |||||||||||||||||
06 | |||||||||||||||||
07 | |||||||||||||||||
08 | |||||||||||||||||
09 | |||||||||||||||||
10 | |||||||||||||||||
11 | |||||||||||||||||
12 | |||||||||||||||||
13 | |||||||||||||||||
14 | |||||||||||||||||
15 | |||||||||||||||||
16 | |||||||||||||||||
17 | |||||||||||||||||
18 | |||||||||||||||||
19 | |||||||||||||||||
20 | |||||||||||||||||
21 | |||||||||||||||||
22 |
先将 GameScene 的 anchorPoint 调整至左上角 (0, 1)
有两种方式:
可以在 GameScene.sks 的属性中修改
可以在 GameViewController.swift 中加入
...
if let scene = SKScene(fileNamed: "GameScene") {
...
scene.anchorPoint = CGPoint(x: 0, y: 1)
...
}
接着来写 GameScene 类别的内容。
设定好 gridXCount
及 gridYCount
常数值。
计算格子的长度及宽度 gridWH
为 萤幕宽度/宽17格。
我们预计会加上许多格子 (格子为正方形),因此可以先建立一个父节点 mapNode
,准备将这些格子加进父节点中。请先设定好它的宽跟高,并将 anchorPoint 定在左上角,加到场景里。
class GameScene: SKScene {
var mapNode: SKSpriteNode?
let gridXCount = 17
let gridYCount = 23
var gridWH = 0
override func didMove(to view: SKView) {
self.backgroundColor = .black
self.gridWH = Int(self.size.width) / gridXCount
self.mapNode = SKSpriteNode(color: .black, size: CGSize(width: CGFloat(self.size.width), height: CGFloat(self.gridWH * gridYCount)))
self.mapNode!.anchorPoint = CGPoint(x: 0, y: 1)
self.addChild(self.mapNode!)
}
}
接着写绘制地图的方法 drawMap
:
for 回圈的第一层先跑Y轴,先取得列,接着将字串用 Array(map[i])
转换成阵列,再依序取得字元。
我们先对 case "w"
贴上一张树的贴图,并设定宽高皆为 gridWH
。
因其 anchorPoint 为 (0.5, 0.5),而父节点的 anchorPoint 为 (0, 1),因此设定 position 的 x 应该为正数,而 y 为负数。除了加/减自己的长度乘以阵列位置的 index,还需加/减回自身长度的一半。
最後加进 mapNode
中。
附注:图片档案需拖拉至左侧档案列表的 Assets.xcassets 里。
func drawMap() {
for i in 0..<gridYCount {
let mapRowArr = Array(map[i])
for j in 0..<gridXCount {
switch mapRowArr[j] {
case "w":
let spriteItem = SKSpriteNode(imageNamed: "tree-green")
spriteItem.anchorPoint = CGPoint(x: 0.5, y: 0.5)
spriteItem.size.width = CGFloat(gridWH)
spriteItem.size.height = CGFloat(gridWH)
spriteItem.position = CGPoint(x: gridWH * j + (gridWH/2), y: -gridWH * i - (gridWH/2))
self.mapNode!.addChild(spriteItem)
default:
break
}
}
}
}
最後呼叫绘制地图的方法:
override func didMove(to view: SKView) {
...
self.drawMap()
}
运行模拟器後可以看到以下成果:
我们来观察用不同模拟器开启的结果
首先,先解决画面超出萤幕的问题,原因在於手机的尺寸不同
我们印出场景的 size
打开 GameScene.sks,发现场景的尺寸是 750x1334
由於 GameViewController.swift 中有设定 scaleMode
,所以画面会缩放,而 iPhone 8 刚好比例与 GameScene.sks 设定的比例一样,所以看起来没有跑版。而 iPhone 11 比例不一样,因此跑版了。
scene.scaleMode = .aspectFill
我们将这一行删除,iPhone 11 画面就可以正常呈现了。
iOS11 之後,多了 Safe Area 功能,他的范围能避开可能会被萤幕切掉或挡住的部分
我们来观察 Main.storyboard View 跟 Safe Area 的差异,Safe Area 比较符合我们想要呈现的方式
试着在 GameViewController.swift 的 viewDidLoad
中取出 Safe Area 的 size、top、bottom
override func viewDidLoad() {
print("size: \(view.safeAreaLayoutGuide.layoutFrame.size)")
print("top: \(view.safeAreaInsets.top)")
print("bottom: \(view.safeAreaInsets.bottom)")
}
在iPhone 11模拟器中印出:
size: (414.0, 896.0)
top: 0.0
bottom: 0.0
发现取出来的数值不是我们预期的结果,原因在於 viewDidLoad
的时间点太早了,还取不到真正的 Safe Area 资讯。覆写另一个 viewSafeAreaInsetsDidChange
试试看:
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
print("size: \(view.safeAreaLayoutGuide.layoutFrame.size)")
print("top: \(view.safeAreaInsets.top)")
print("bottom: \(view.safeAreaInsets.bottom)")
}
在iPhone 11模拟器中印出:
size: (414.0, 818.0)
top: 44.0
bottom: 34.0
成功取得 Safe Area 的资讯了!
查看官网的说明,这个方法能在 Safe Area 改变时通知 view controller,因此我们必须在这个时间点调整节点的位置。
applySafeArea
方法,把 mapNode
的位置往下校正回来(topSafeArea)class GameScene: SKScene {
var topSafeArea: CGFloat = 0
var bottomSafeArea: CGFloat = 0
...
func applySafeArea() {
if #available(iOS 11.0, *) {
if let view = self.view {
self.topSafeArea = view.safeAreaInsets.top
self.bottomSafeArea = view.safeAreaInsets.bottom
}
}
if let mapNode = self.mapNode {
mapNode.position = CGPoint(x: 0, y: -self.topSafeArea)
}
}
}
viewDidLoad
方法,在这边我们一样呈现 GameScene,并把 gameScene 储存起来。viewSafeAreaInsetsDidChange
方法时,判断确定取得到 gameScene 後,呼叫 applySafeArea
方法class GameViewController: UIViewController {
var gameScene: GameScene?
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
if let scene = self.gameScene {
scene.applySafeArea()
}
}
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as? SKView {
if let scene = SKScene(fileNamed: "GameScene") {
self.gameScene = scene as? GameScene
...
}
...
}
}
}
请特别注意!不能把原本写在
viewDidLoad
的程序整个搬进viewSafeAreaInsetsDidChange
。
viewDidLoad
在生命周期里只会呼叫一次,而viewSafeAreaInsetsDidChange
则是当 Safe Area 变动时就会再次呼叫,像是旋转手机,就会发生改变。若是整个程序都搬进去,当旋转手机时,游戏就会重来罗!
成功绘制完迷宫了!
明天来带大家美化迷宫~
<<: Spring boot 配置 Fluent bit 传递 Log
>>: Day04 X Core Web Vital & RAIL Model
今天迈入第7天,根据计画,前几天我们介绍了一些登录档的基础知识和前置作业,假设读者跟笔者一样略懂略懂...
今天我们要来将 Chatbot 与 Language Understanding Service (...
若你有用过 github 的话,对於仓库 Repository 的概念想必是不陌生。它就是一个存放各...
今天来写点杂记和更多的 Leetcode :) 11.7 把树对应到 Hamming Distanc...
嗨!我是莉莉,目前是个软件工程师。去年因为公司内部任务接触到和资安相关的议题,开始对资讯安全感兴趣、...