Corona SDK tutorial: game Flappy Bat phần 3
Đây là phần cuối của loạt bài hướng dẫn làm quen với corona SDK: game Flappy Bat Trong bài này mình sẽ trình bày về cơ chế xử lý va chạm và DB trong corona SDK. ở bài viết phần 1: https://viblo.asia/TienNM87/posts/ojaqG0oOMEKw chúng ta đã có thể tạo được scene menu và in game, tạo chuyển động ...
Đây là phần cuối của loạt bài hướng dẫn làm quen với corona SDK: game Flappy Bat Trong bài này mình sẽ trình bày về cơ chế xử lý va chạm và DB trong corona SDK.
ở bài viết phần 1:
https://viblo.asia/TienNM87/posts/ojaqG0oOMEKw
chúng ta đã có thể tạo được scene menu và in game, tạo chuyển động cho nền
và phần 2
https://viblo.asia/TienNM87/posts/YrEBRAkNG8Zj
chúng ta đã có thể handle được input touch vào màn hình và sử dụng module vật lý của corona để tạo chuyển động cho nhân vật, đồng thời cũng đã tạo được cơ chế sinh chướng ngại vật là các ống cống với độ cao random. Mời các bạn đọc lại các bài viết trên để hiểu rõ chi tiết hơn
Toàn bộ source code & assets của dự án ở đây, bạn đọc có thể download về build chạy thử:
https://github.com/TienHP/Report1507/tree/develop
Để kết thúc game cần có điều kiện va chạm giữa nhân vật và ống cống Ngoài ra khi kết thúc game chúng ta cần có 1 popup để thông báo kết quả, phần thông báo kết quả gồm có SCORE: (điểm số) và BEST: (điểm số cao nhất), chúng ta cần lưu trữ BEST vào 1 file .txt để lưu vào bộ nhớ trong của device.
**I > Về va chạm trong corona **
Muốn kiểm tra va chạm trong corona, ở đây là scene game.lua Chúng ta thực hiện như sau:
Trong scene game.lua ở function scene:show(event) chúng ta thêm lệnh:
Runtime:addEventListener("collision", onCollision)
Mặc dù là code lua nhưng nhìn khá giống với 1 câu lệnh của java, mục đích của nó cũng rất rõ ràng , đăng ký xử lý sự kiện ‘collision’ với đối tượng Runtime trong function ‘onCollision’ Function ở đây onCollision như sau:
function onCollision( event ) if ( event.phase == "began" ) then composer.gotoScene( "restart" ) end end
‘collision’ là sự kiện xảy ra khi có va chạm giữa 2 ‘body’ có thể hiểu là 2 đối tượng chịu sự tác động của vật lý, vật lý là 1 component của corona được add vào thế giới game (gameworld) khi sử dụng lệnh:
local physics = require "physics"
khi đó với mỗi đối tượng, chẳng hạn như player chúng ta sử dụng lệnh sau để add ‘body’ :
physics.addBody(player, "static", {density=.1, bounce=0.1, friction=1})
các params ở đây là body type – static (tĩnh) , density (mật độ), bounce(độ nảy), friction(ma sát) những yếu tố này ảnh hưởng đến chuyển động của vật rắn (body) trong thế giới vật lý. Để lấy ra 1 chuẩn, thường thì đặt player density = 0.1. bounce = 0.1 & friction = 1 Và các đối tượng khác dựa trên player để tham chiếu.
Trở lại với function onCollision, khi xảy ra va chạm giữa 2 body ở đây là player và các ống cống (đã được tạo ở phần trước) với lệnh:
physics.addBody(topColumn, "static", {density=1, bounce=0.1, friction=.2})
chú ý ở hàm flyUp khi nhấn vào màn hình chúng ta đã có lệnh:
player.bodyType = "dynamic"
để thay đổi về dynamic, khi có va chạm xảy ra giữa chim và ống cống method onCollision sẽ được physics triệu gọi, ở đây xử lý chỉ là show kết quả, chúng ta next sang scene restart và thông báo ra 1 popup với người chơi bằng lệnh
composer.gotoScene( "restart" )
II > Cơ chế lưu trữ đơn giản bằng file text
Trong scene restart này chúng ta cần show 1 popup thông báo kết quả gồm có SCORES và BEST Để tạo, chúng ta dùng function drawScene:
function scene:create( event ) print( 'restart scene created' ) local sceneGroup = self.view drawScene( sceneGroup ) registerEvents() end
function drawScene( sceneGroup ) -- body background = display.newImageRect("bg.png",900,1425) background.anchorX = 0.5 background.anchorY = 0.5 background.x = display.contentCenterX background.y = display.contentCenterY sceneGroup:insert(background) gameOver = display.newImageRect("gameOver.png",500,100) gameOver.anchorX = 0.5 gameOver.anchorY = 0.5 gameOver.x = display.contentCenterX gameOver.y = display.contentCenterY - 400 gameOver.alpha = 0 sceneGroup:insert(gameOver) restart = display.newImageRect("start_btn.png",300,65) restart.anchorX = 0.5 restart.anchorY = 1 restart.x = display.contentCenterX restart.y = display.contentCenterY + 400 restart.alpha = 0 sceneGroup:insert(restart) scoreBg = display.newImageRect("menuBg.png",480,393) scoreBg.anchorX = 0.5 scoreBg.anchorY = 0.5 scoreBg.x = display.contentCenterX scoreBg.y = display.contentHeight + 500 sceneGroup:insert(scoreBg) scoreText = display.newText(data.score,display.contentCenterX + 70, display.contentCenterY - 60, native.systemFont, 50) scoreText:setFillColor(0,0,0) scoreText.alpha = 0 sceneGroup:insert(scoreText) bestText = score.init({ fontSize = 50, font = "Helvetica", x = display.contentCenterX + 70, y = display.contentCenterY + 85, maxDigits = 7, leadingZeros = false, filename = "scorefile.txt", }) bestScore = score.get() bestText.text = bestScore bestText.alpha = 0 bestText:setFillColor(0,0,0) sceneGroup:insert(bestText) end
function này draw 1 popup để thông báo score như sau:
Để lưu trữ best score, chúng ta xây dựng module score.lua như sau:
local M = {} function M.init( options ) local customOptions = options or {} local opt = {} opt.fontSize = customOptions.fontSize or 24 opt.font = customOptions.font or native.systemFontBold opt.x = customOptions.x or display.contentCenterX opt.y = customOptions.y or display.contentCenterY opt.maxDigits = customOptions.maxDigits or 6 opt.leadingZeros = customOptions.leadingZeros or false M.fileName = customOptions.fileName or 'score.txt' local prefix = ' if opt.leadingZeros then prefix = '0' end M.formatStr = '%' .. prefix .. opt.maxDigits .. 'd' M.scoreText = display.newText( string.format( M.formatStr, 0), opt.x, opt.y, opt.font, opt.fontSize ) return M.scoreText end function M.set( value ) -- body M.score = value M.scoreText.text = string.format( M.formatStr, value ) end function M.get () return M.score end function M.add (value) M.score = M.score + value M.scoreText.text = string.format( M.formatStr, M.score ) end function M.save() local path = system.pathForFile( M.fileName, system.DocumentsDirectory ) local file = io.open( path, 'w' ) if file then local content = tostring( M.score ) file:write( content ) io.close( file ) return true else print( 'Error: could not read', M.fileName, '.') return false end end function M.load() local path = system.pathForFile( M.fileName, system.DocumentsDirectory ) local file = io.open( path, 'r' ) if file then local content = file:read('*a') local score = tonumber( content ) io.close( file ) return score end print ('Error: could not read from file', M.fileName, '.') return nil end return M
đây là module gồm các function làm việc với file db như save, load corona cung cấp các function để làm việc với file stream nằm trong đối tượng system với câu lệnh sau để mở và ghi file:
local path = system.pathForFile( M.fileName, system.DocumentsDirectory ) local file = io.open( path, 'w' )
muốn sử dụng lệnh ghi:
file:write( content )
hoặc sử dụng lệnh đọc
local content = file:read('*a')
ở đây ‘*a’ là mode truyền vào với chỉ định đọc toàn bộ file, bắt đầu từ vị trí hiện tại Với các lệnh read, write ta có thể xây dựng được 1 DB đơn giản cho game flappy bat như trên. Chúng ta áp dụng module score.lua vào scene restart như sau:
function drawScene( sceneGroup ) .......... bestText = score.init({ fontSize = 50, font = "Helvetica", x = display.contentCenterX + 70, y = display.contentCenterY + 85, maxDigits = 7, leadingZeros = false, filename = "scorefile.txt", }) bestScore = score.get() bestText.text = bestScore bestText.alpha = 0 bestText:setFillColor(0,0,0) sceneGroup:insert(bestText) end
function để save best score như sau:
function saveScore( ) -- body local prevScore = score.load() if prevScore then if prevScore < data.score then score.set(data.score) else score.set(prevScore) end else score.set(data.score) end score.save() end
function này gọi sau khi nhấn vào popup restart
function registerEvents( ) -- body restart:addEventListener( 'touch', restartGame ) end function restartGame( event ) -- body if event.phase == 'ended' then saveScore() composer.gotoScene( 'start' ) end end
Như vậy là chúng ta đã có thể lưu trữ được điểm số của người chơi trong 1 file DB tự tạo, đối với các loại dữ liệu phức tạp hơn chúng ta có thể sử dụng tới SQLite với module sqlite trong corona sdk hỗ trợ sẵn, tuy nhiên đối với các loại dữ liệu như điểm số, tên người chơi thì hệ thống file text cũng đã đáp ứng được yêu cầu.
Như trên mình đã trình bày tất cả những hiểu biết về engine Corona SDK qua việc xây dựng 1 game nhỏ demo Flappy Bat, trong quá trình tìm hiểu có rất nhiều hướng mở rộng về các component của corona SDK như physics engine Box2D, sqlite, network, composer, in-app purchase … bản than corona SDK là 1 engine rất mạnh, dựa trên ngôn ngữ Lua rất mềm dẻo, linh hoạt, với các công cụ ngày càng được hoàn thiện, đã và đang trở thành 1 đối thủ đáng gờm cho các game engine hiện nay.
Hẹn gặp lại corona SDK vào 1 tương lai không xa!