12/08/2018, 11:22

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:

Screen Shot 2015-08-31 at 11.53.33 PM.png

Để 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!

0