Swift_Tetris game - part 7
Bài đăng này đã không được cập nhật trong 9 năm
Thiết lập điểm số và hoàn thiện game
Bài trước ta đã hoàn thiện hết các chức năng di chuyển và điều khiển các khối hình, thiết lập các logic cho game. Và trong bài cuối cùng này, ta sẽ hoàn thiện nốt phần còn lại: Tính điểm số cho người chơi và hoàn thiện giao diện cho game.
Trước tiên ta add thêm 1 số đối tượng trên màn hình bao gồm ô tính điểm, ô tính level và khối block sẽ rơi tiếp theo sau khi khối hình hiện tại kết thúc chuyển động. Mở Main.storyboard
và thiết lập như trong hình
Tương tự như bài trước, ta sẽ add thêm 1 đối tượng view vào màn hình bằng cách kéo thả đối tượng view vào màn hình, sau đó ta sẽ thiết lập vị trí và kích thước cho nó như hình sau đây
Tiếp đó ta add 1 Image View
vào đối tượng View
mà ta vừa thêm vào bên trên. Ta set vị trí cho nó là 0, 0 và kích thước là 84 x 100. Mở "Attributes Inspector", ta set thuộc tính Image
cho Image View
là whitebg.png
ta tiếp tục add thêm label cho đối tượng View
vừa add bên trên. Set location cho nó là 7, 20
và kích thước là 70, 21
. Thiết lập các thuộc tính cho nó như hình dưới đây
Copy đối tượng label
vừa tạo bên trên xuống ngay dưới nó và đặt location cho nó là 0, 45
và kích thước là 84, 39
, set text là 999
và các thuộc tính thiết lập như sau
Sau đó ta copy cả đối tượng View đã thiết lập ở trên, chỉnh lại vị trí thành 224, 237
và màu sắc mới cho label, sửa text thành LEVEL
Tương tự như bài trước, để tạo thao tác cho ô điểm số, ta cũng mở Assistant Editor
bằng cách click vào biểu tượng 2 dấu tròn ở góc trên bên phải của Xcode. Giữ Control và kéo thả đối tượng 999
đầu tiên vào của sổ GameViewController
, đặt tên hàm là scoreLabel
.Chú ý là thuộc tính Storage được set thành Weak
Thực hiện thao tác như trên với đối tượng 999
thứ 2 và đặt tên hàm là levelLabel
. Chạy thử ứng dụng ta đã thấy giao diện đã khá hoàn thiện
Bây giờ, để cho game thêm sinh động, ta sẽ thêm hiệu ứng các khối hình nổ khi 1 hàng được lấp đầy. Ta cần thêm 1 vài đoạn code trong GameScene.swift
ngay bây giờ .
-Chúng ta lại thêm tuple
để Swiftris
trả về mỗi khi 1 dòng bị ấp đầy, và GameViewController
luôn truyền các giá trị này đến GameScene
để gây nên các hiệu ứng mỗi khi 1 dòng được lấp đầy
func animateCollapsingLines(linesToRemove: Array<Array<Block>>, fallenBlocks: Array<Array<Block>>, completion:() -> ()) {
var longestDuration: NSTimeInterval = 0
Với mỗi khồi block hiện tại đang rơi xuống vị trí mới, ta sẽ tính từ trái sang phải và duỵệt theo từng côt và từng khối.Ta khai báo 1 biến longestDuration
để xác định khoảng thời gian chính xác ta phải đợi trước khi gọi completion
var longestDuration: NSTimeInterval = 0
for (columnIdx, column) in enumerate(fallenBlocks) {
for (blockIdx, block) in enumerate(column) {
let newPosition = pointForColumn(block.column, row: block.row)
let sprite = block.sprite!
Khi xoá bỏ 1 dòng khi dòng đó được khối hình lấp đầy, ta sẽ tạo hiệu ứng cho chúng vỡ ra và bắn ra khỏi màn hình như 1 vụ nổ vậy. Để làm được điều này, ta sử dụng UIBezierPath
. Ta sử dụng 1 bán kính được xác định ngẫu nhiên để xác định kích thước của khối nổ, và ta cũng sử dụng phép ngẫu nhiên cho những phương hướng bay khác nhau của các mảnh vỡ.
for (rowIdx, row) in enumerate(linesToRemove) {
for (blockIdx, block) in enumerate(row) {
let randomRadius = CGFloat(UInt(arc4random_uniform(400) + 100))
let goLeft = arc4random_uniform(100) % 2 == 0
var point = pointForColumn(block.column, row: block.row)
point = CGPointMake(point.x + (goLeft ? -randomRadius : randomRadius), point.y)
let randomDuration = NSTimeInterval(arc4random_uniform(2)) + 0.5
Tiếp theo ta sẽ chọn 1 góc khởi điểm để các mảnh vỡ bay ra. Tất cacr các giá trị này đều được tính theo radian theo hình dưới đây. Khi mảnh vỡ bay từ trái sang phải thì giá trị đầu sẽ là 0
và giá trị cuối sẽ là π
và ngược lại, bay từ phải sang trái thì giá trị đầu là π
và giá trị cuối sẽ là 2π
let randomDuration = NSTimeInterval(arc4random_uniform(2)) + 0.5
var startAngle = CGFloat(M_PI)
var endAngle = startAngle * 2
if goLeft {
endAngle = startAngle
startAngle = 0
}
let archPath = UIBezierPath(arcCenter: point, radius: randomRadius, startAngle: startAngle, endAngle: endAngle, clockwise: goLeft)
let archAction = SKAction.followPath(archPath.CGPath, asOffset: false, orientToPath: true, duration: randomDuration)
archAction.timingMode = .EaseIn
let sprite = block.sprite!
Tiếp theo ta thực hiện hiệu ứng nổ cho các spite block và sau đó các khối hình còn lại sẽ rơi xuống chiếm chỗ các khối hình vừa bị nổ
sprite.zPosition = 100
sprite.runAction(
SKAction.sequence(
[SKAction.group([archAction, SKAction.fadeOutWithDuration(NSTimeInterval(randomDuration))]),
SKAction.removeFromParent()]))
Để các khối còn lại rơi xuống vị trí mới sau khi các khối cũ nổ, ta gọi completion
runAction(SKAction.waitForDuration(longestDuration), completion:completion)
Bây giờ ta sẽ thêm âm thanh cho game thêm sinh động. Trong bài đầu tiên ta đã add một số file mp3 vào thư mục Sounds. Chúng ta sẽ thêm 1 vài đoạn code vào GameScene
để phát âm thanh.
Theme song sẽ được phát liên tục ko ngừng cho đến khi ta thoát ứng dụng. Chúng ta khai báo 1 vòng lặp cho theme song chạy lặp đi lặp lại vô tận. Tiếp đó ta thêm 1 function để GameViewController
load các file âm thanh tương ứng với từng trường hợp.
runAction(SKAction.repeatActionForever(SKAction.playSoundFileNamed("theme.mp3", waitForCompletion: true)))
func playSound(sound:String) {
runAction(SKAction.playSoundFileNamed(sound, waitForCompletion: false))
}
Chạy thử game và ta đã nghe thấy âm thanh đã được chạy liên tục. Bây giờ ta sẽ thêm một vài login cuối cùng để hoàn thiện game.
Với điểm số và level, khi game bắt đầu, ta cần reset về giá trị ban đầu và bắt đầu với TickLengthLevelOne
. Ta thêm một vài dòng khai báo sau vào đầu function gameDidBegin
trong GameViewController
evelLabel.text = "\(swiftris.level)"
scoreLabel.text = "\(swiftris.score)"
scene.tickLengthMillis = TickLengthLevelOne
Và khi game over, ta sẽ phát âm thanh báo hiệu và sẽ xoá tất cả các block hiện có trên màn hình trước khi game mới bắt đầu. Ta thêm vài dòng code sau vào function gameDidEnd
scene.playSound("gameover.mp3")
scene.animateCollapsingLines(swiftris.removeAllBlocks(), fallenBlocks: Array<Array<Block>>()) {
swiftris.beginGame()
}
Mỗi khi user lên level, chúng ẽ tăng tốc độ rơi của block, nghĩa là giảm giá trị tick interval
, và mỗi khi lên 1 level, ta sẽ giảm giá trị này đi 100 mili giây. Và đừng quên phát âm thanh báo hiệu rằng người chơi đã lên level.
Ta cập nhật function gameShapeDidLevelUp
levelLabel.text = "\(swiftris.level)"
if scene.tickLengthMillis >= 100 {
scene.tickLengthMillis -= 100
} else if scene.tickLengthMillis > 50 {
scene.tickLengthMillis -= 50
}
scene.playSound("levelup.mp3")
Khi 1 dòng được lấp đầy, ta sẽ gọi hàm removeCompletedLines
để xoá toàn bộ những dòng bị lấp đầy trên màn hình, đồng thời ta sẽ cập nhật điểm số và xuất hiện khối block mới.
let removedLines = swiftris.removeCompletedLines()
if removedLines.linesRemoved.count > 0 {
self.scoreLabel.text = "\(swiftris.score)"
let removedLines = swiftris.removeCompletedLines()
if removedLines.linesRemoved.count > 0 {
self.scoreLabel.text = "\(swiftris.score)"
scene.animateCollapsingLines(removedLines.linesRemoved, fallenBlocks:removedLines.fallenBlocks)
Đến đây ta đã hoàn thành xong việc xây dựng game xếp hình huyền thoại bằng ngôn ngữ mới Swift. Qua chuỗi bài vừa qua, ta đã biết thêm được khá nhiều kiến thức mới, học và làm quen với 1 ngôn ngữ rất mới của Apple. Bên cạnh đó ta còn học
All rights reserved