Corona SDK tutorial: game Flappy Bat phần 1
I - Giới thiệu Tiếp tục trong mạch bài viết về Corona SDK, người viết sẽ trình bày những kiến thức căn bản về corona engine qua 1 game phổ biến mỗi khi bạn start engine mới hiện nay, đó là game Flappy Bird. Flappy Bird là 1 game thuộc thể loại casual được Nguyễn Hà Đông tạo ra, đã trở thành 1 ...
I - Giới thiệu
Tiếp tục trong mạch bài viết về Corona SDK, người viết sẽ trình bày những kiến thức căn bản về corona engine qua 1 game phổ biến mỗi khi bạn start engine mới hiện nay, đó là game Flappy Bird.
Flappy Bird là 1 game thuộc thể loại casual được Nguyễn Hà Đông tạo ra, đã trở thành 1 cơn sốt trong suốt 1 thời gian dài ở việt nam và quốc tế, cách chơi của game này rất đơn giản, bạn chỉ việc chạm màn hình để giữ chú chim không bị rơi xuống đất và vượt qua các chướng ngại vật nhiều nhất có thể.
Với tài nguyên được cung cấp sẵn, chúng ta có thể sử dụng Corona dể tạo ra 1 game tương tự đó là game Flappy Bat Nếu sử dụng điện thoại Android bạn đọc có thể tải bản build về chạy thử:
https://drive.google.com/file/d/0B5ZgM3GuNcA1akFubXNkY3I0RHM/view?usp=sharing
Việc làm quen với Corona và công cụ Simulator, bạn đọc có thể tham khảo ở bài viết trước:
https://viblo.asia/TienNM87/posts/1l0rvmByRyqA
Bài viết này sẽ chỉ ra cách sử dụng Corona để giải quyết các vấn đề trong game Flappy Bat, các kiến thức cần thiết sẽ được cung cấp để bạn đọc tham khảo thêm
II - Project
Bạn tải các assets của game tại đây: https://drive.google.com/file/d/0B5ZgM3GuNcA1dlJDM1Z2S2F5VDA/view?usp=sharing Bao gồm các ảnh sẽ sử dụng trong trò chơi, bạn cần copy tất cả ảnh vào thư mục project đã tạo.
Như bạn đọc đã biết, Corona sử dụng 1 số các file đặc biệt để cấu hình project, một số thông tin như orientation, with height của content area, scaling mode .. trong 1 số file đặc biệt việc đầu tiên khi tạo project đó là phải tạo ra các file đặc biệt này đó là
--build.settings settings = { orientation = { default = "portrait", supported = { "portrait", }, }, }
--config.lua local ratio = display.pixelHeight/display.pixelWidth application = { content = { awidth = ratio > 1.5 and 800 or math.ceil(1200/ratio), height = ratio < 1.5 and 1200 or math.ceil(800*ratio), scale = 'letterbox', fps = 30, imageSuffix = { ['@2x'] = 1.3, },--end suffix },--end content } -- end application
--main.lua display.setStatusBar( display.HiddenStatusBar )
với file build.settings đó là nơi lưu trữ các thông tin để corona build thành game, ở đây chỉ cần thông tin về orientation nên chúng ta set orientation là 'portrait'
với file config.lua là nơi lưu trữ những thuộc tính về content area (vùng nội dung) và scaling mode (chế độ hiển thị) những khái niệm này người đọc có thể tham khảo ở đây
https://docs.coronalabs.com/daily/guide/basics/configSettings/index.html
Ở đây chúng ta sử dụng content area tương ứng với 2 loại màn hình đó là ratio (h/w) > 1.5 (có vẻ dài) < 1.5 (có vẻ ngắn)
với tỉ lệ > 1.5 thì content area là 800 x (800*ratio)
với tỉ lệ < 1.5 thì content area là 1200/ratio x 1200
scaling mode là 'letterbox' cho chúng ta giữ nguyên tỉ lệ ratio trên các màn hình khác nhau, do vậy kết hợp với điều kiện trên ta sẽ được content are full screen với tất cả các màn hình.
Với file main.lua, đây chính là entry của chương trình vì vậy chúng ta khởi chạy một số tác vụ cần thiết khi start game ở đây đơn giản chúng ta chỉ ấn status bar cho đẹp.
Trong quá trình phân tích game flappy bird ta nhận thấy:
game có 3 screen chính
- screen start game: có background, nền chạy, nút start, nhân vật chúng ta sẽ tạo ra màn hình start game như sau:
- screen game chính: có background, nền chạy, nhân vật, chướng ngại vật như sau
- screen restart game: có background, bảng thông báo điểm, nút restart như sau:
Với việc phân tích ban đầu như vậy, chúng ta sẽ tạo ra 3 scene(cảnh) tương ứng cho mỗi màn hình, lần lượt là start, game và restart
Trong corona, mỗi scene(cảnh) được tạo bởi 1 file .lua và được quản lý bởi 1 đối tượng được gọi là composer, đối tượng này chịu trách nhiệm với các tác vụ liên quan đến scene như add scene, remove scene ..
Chúng ta sẽ tạo ra 3 empty file là start.lua, game.lua và restart.lua
Khi bắt đầu trò chơi thì chuyển đến scene start do vậy chúng ta cần sửa file main.lua như sau
display.setStatusBar( display.HiddenStatusBar ) local composer = require "composer" composer.gotoScene( "start" )
chỉ việc gọi composer để thực hiện việc chuyển scene
Tuy nhiên đến đây chương trình không thể chạy được, nguyên do là file start.lua cần phải được định nghĩa như 1 scene chứ không phải giống như 1 file .lua bình thường, chúng ta cần sửa lại file start.lua như sau
local composer = require( "composer" ) local scene = composer.newScene() function scene:create( event ) end function scene:show( event ) end function scene:hide( event ) end function scene:destroy( event ) end -- Listener setup scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) return scene
Tác vụ ở đây đó là đăng kí sử lí các sự kiện 'create' - tạo scene, 'show' - hiện scene, 'hide' - ẩn scene, 'destroy' - ẩn scene
khi gọi câu lện composer.newScene() sẽ dẫn tới các sự kiện lần lươt là create => show
Như vậy các tác vụ về khởi tạo và draw scene chúng ta nên viết trong method create. Các công việc đó là : draw background, draw nền, draw player và draw nút start Chúng ta thực hiện các công việc này như sau (trong method create)
local physics = require 'physics' physics.start() -- Called when the scene's view does not exist: function scene:create( event ) local sceneGroup = self.view drawScene(sceneGroup) end function drawScene( sceneGroup ) --draw the background background = display.newImageRect('bg.png', 900, 1425) background.anchorX = 0.5 background.anchorY = 1 background.x = display.contentCenterX background.y = display.contentHeight sceneGroup:insert(background) --draw the platform platform = display.newImageRect('platform.png',900,53) platform.anchorX = 0 platform.anchorY = 1 platform.x = 0 platform.y = display.viewableContentHeight - 110 physics.addBody(platform, "static", {density=.1, bounce=0.1, friction=.2}) platform.speed = 4 sceneGroup:insert(platform) platform2 = display.newImageRect('platform.png',900,53) platform2.anchorX = 0 platform2.anchorY = 1 platform2.x = platform.awidth platform2.y = display.viewableContentHeight - 110 physics.addBody(platform2, "static", {density=.1, bounce=0.1, friction=.2}) platform2.speed = 4 sceneGroup:insert(platform2) --draw the start button startBtn = display.newImageRect("start_btn.png",300,65) startBtn.anchorX = 0.5 startBtn.anchorY = 1 startBtn.x = display.contentCenterX startBtn.y = display.contentHeight - 400 sceneGroup:insert(startBtn) --draw the title group --draw the title title = display.newImageRect("title.png", 500, 100) title.anchorX = 0.5 title.anchorY = 0.5 title.x = display.contentCenterX - 80 --title.y = display.contentCenterY sceneGroup:insert(title) p_options = { awidth = 80, height = 42, numFrames = 2, sheetContentWidth = 160, sheetContentHeight = 42, } --draw player playerSheet = graphics.newImageSheet('bat.png', p_options) sequenceData = { name = 'player', start = 1, count = 2, time = 500, } player = display.newSprite(playerSheet, sequenceData) player.anchorX = 0.5 player.anchorY = 0.5 player.x = display.contentCenterX + 240 --player.y = display.contentCenterY player:play() sceneGroup:insert(player) titleGroup = display.newGroup() titleGroup.anchorChildren = true titleGroup.anchorX = 0.5 titleGroup.anchorY = 0.5 titleGroup.x = display.contentCenterX titleGroup.y = display.contentCenterY - 250 titleGroup:insert(title) titleGroup:insert(player) sceneGroup:insert(titleGroup) titleAnimation() end
Một số điểm chú ý trong đoạn code trên:
- Muốn draw image trong Corona chúng ta thực hiện lệnh display.newImageRect() - với 2 tham số with, height, bức ảnh sẽ được stretch (kéo giãn) để full với hình chữ nhật, trong đa số trường hợp awidth height chính là size của bức ảnh.
- Thiết lập anchorX & anchorY để xác định pivot của đối tượng hình ảnh, kiến thức này bạn đọc có thể tham khảo ở đây https://docs.coronalabs.com/guide/graphics/transform-anchor.html
- Thết lập x và y để xác định vị trí đối tượng trên content Area
- Muốn draw 1 sprite trong Corona chúng ta thực hiện lệnh display.newSprite() - với tham số truyền vào là spriteSheet và các options chi tiết về method này bạn đọc có thể tham khảo ở đây: https://docs.coronalabs.com/api/library/display/newSprite.html
- Các đối tượng có thể được group lại, việc group giúp ich cho việc thao tác trên nhiều đối tượng ví dụ như thay đổi vị trí, ẩn hiện ...
với các công cụ trên ta dễ dàng xây dựng được một scene 2D tuỳ ý. Việc draw scene coi như là được thực hiện xong.
Công việc tiếp theo ở scene này là chạy platform ở dưới, thực chất thì platform được tạo ra bởi 2 image rect:
--draw the platform platform = display.newImageRect('platform.png',900,53) platform.anchorX = 0 platform.anchorY = 1 platform.x = 0 platform.y = display.viewableContentHeight - 110 physics.addBody(platform, "static", {density=.1, bounce=0.1, friction=.2}) platform.speed = 4 sceneGroup:insert(platform) platform2 = display.newImageRect('platform.png',900,53) platform2.anchorX = 0 platform2.anchorY = 1 platform2.x = platform.awidth platform2.y = display.viewableContentHeight - 110 physics.addBody(platform2, "static", {density=.1, bounce=0.1, friction=.2}) platform2.speed = 4 sceneGroup:insert(platform2)
với position là x1 = 0, x2 = platform.with nghĩa là được xếp cạnh nhau, mục đích của việc này là khi platform1 di chuyển sang trái, platform2 cũng di chuyển và khi platform1 di chuyển hết chiều dài của nó, nó sẽ được move về phía cuối của platform2 mà trong lúc đó người chơi vẫn nhìn thấy platform2 đang di chuyển, khi platform2 di chuyển hết chiều dài của nó thì nó lại được move đến cuối platform1 do vậy sẽ tạo ra cảm giác platform dài vô tận chạy mãi không hết! Để thực hiện việc này chúng ta cần sửa lại như sau:
-- Called immediately after scene has moved onscreen: function scene:show( event ) local group = self.view phase = event.phase if phase == 'will' then elseif phase == 'did' then --move the platform platform.enterFrame = scrollGround Runtime:addEventListener( 'enterFrame', platform ) platform2.enterFrame = scrollGround Runtime:addEventListener( 'enterFrame', platform2 ) startBtn:addEventListener( 'touch', startGame ) end print("entered") end function scrollGround( self, event ) if self.x - self.speed < -900 then self.x = 900 - (-900 - self.x + self.speed) else self.x = self.x - self.speed end end
công việc chạy background cần thực hiện trong mỗi frame update do vậy chúng ta cần sử dụng sự kiện 'enterFrame', mõi khi vào 1 frame mới, thì thực hiện method scrollGround. Ở đây chiều rộng mỗi platform là 900 nên khi platform 1 di chuyển hết (self.x - self.speed < -900) thì nó được move tới vị trí cuối platform2 (900 - (-900 - self.x + self.speed)) và ngược lại với platform2.
Công việc còn lại ở scene này là add xử lí sự kiện click button start, chúng ta thực hiện như sau:
--method scene:show startBtn:addEventListener( 'touch', startGame ) function startGame( event ) -- body if event.phase == 'ended' then composer.gotoScene( 'game' ) end end
chúng ta cần đăng kí xử lí cho sự kiện 'touch' sử dụng method addEventListener(). Tác vụ ở đây đơn giản là chuyển scene sử dụng composer.
OK, như vậy màn hình start game đã được tạo xong, bạn đọc có thể sử dụng simulator để test thử hoạt động. Trong các phần tiếp theo mình sẽ trình bày cách tạo scene game và scene restart game. Thanks!