Swift_Tetris game - part 7

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

Image

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

Image

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 Viewwhitebg.png

Image

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

Image

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

Image

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

Image

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

Image

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 😃

Image

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à

Image

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