5-2 Cuộc đua của Punya (2)
Bài đăng này đã không được cập nhật trong 3 năm
5-2 Cuộc đua của Punya (2)
Ở phần này, chúng ta sẽ thêm vào trò chơi trước những tính năng như hình chủ đề, màn hình menu. Hình chủ đề tên game và màn hình menu là những thứ không thể thiếu trong game. Phần này sẽ giới thiệu những khái niệm về màn hình, những thuật di chuyển màn hình và vừa làm màn hình menu
Phân biệt các loại màn hình và Scene class
Trong game, chúng ta có thể chia ra nhiều cảnh. Ví dụ như cảnh menu, cảnh trong game. Sắp xếp cấu trúc của chương trình, sắp xếp chương trình theo từng cảnh sẽ làm cho cả chương trình dễ nhìn hơn và cũng dễ quản lý hơn. Trong MyGame chúng ta được chuẩn bị Scene class rất tiện lợi để tạo nên các cảnh là [Scene::Base]. Khi sử dụng [Scene::Base] này chúng ta có thể dễ dàng tạo nên những cảnh phức tạp và có thể quản lý các cảnh một cách rất đơn giản. Dưới đây tôi sẽ giới thiệu phương pháp tạp Scene sử dụng [Scene::Base]
Sử dụng Scene Class
Scene Class sử dụng [Scene::Base] sẽ được viết như dưới đây.
require 'mygame/boot'
class tên class<Scene::Base
def init
#xử lý dữ liệu ban đầu
end
def quit
# kết thúc xử lý
end
def update
# làm mới xử lý
end
def render
# xử lý hình ảnh
end
end
#Chạy Scene Class
Scene.main_loop [Tên class]
Đây là chương trình sử dụng scene class. Có 4 lệnh để định nghĩa Scene Class [init][quit][update][render] được viết ra để tạo chương trình.
Phần dưới đây sẽ vừa tạo một số cảnh, vừa giải thích cách sử dụng Scene Class.
Quyết định cấu trúc của các scene
Trước khi chúng ta bắt tay vào lập trình thì hãy cùng nghĩ cấu trúc của các cảnh trong game thôi. Dưới đây là hình vẽ đại khái những hình ảnh sẽ có trong game.
Hình logo -> Hình tên game -> Game menu -> Cuộc đưa của các con punya. Trong màn hình menu thì chúng ta sẽ thiết kế hai nút là "Quay lại" và "Giới thiệu nhân vật". Kể cả một hình đơn giản như thế nào thì khi chúng ta vẽ thử ra giấy, chi tiết hóa hình ảnh thì lúc đó chusg ta mới có thể bắt đầu công việc lập trình.
Màn hình logo
Chúng ta cùng thử tạo màn hình hiển thị hình logo nhé. Để tạo hình, chúng ta dùng [Scene::Base] Vì màn hình này hiển thị logo nên chúng ta đặt tên là [LogoScene].
punyamenu00.rb
require 'mygame/boot'
class LogoScene < Scene::Base
def init
@logo = Image.new("image/logo.png")
end
def render
@logo.render
end
end
Scene.main_loop do LogoScene
Như vậy chúng ta đã vẽ xong màn hình Logo.
Hình 5-11 Vẽ hình logo
Hãy nhìn lệnh [init]. Đây là lệnh được thực hiện chỉ một lần để viết những xử lsy ban đầu đầu vào của hình ảnh. Tiếp theo hãy nhìn lệnh [render]. Tại đây, những xử lý vẽ hình được viết vào. Như vậy tại đây có nghĩa là hình ảnh được [init] tạo ra sẽ được vẽ lên.
Cuối cùng, chúng ta cũng có dòng này
Scene.main_loop LogoScene
Tại dòng này là xử lý cần thiết để thực hiện Scene Class. Argument của [Scene.main_loop] được trao cho chính là hình ảnh chúng ta đẫ định nghĩa tại Scene Class. Tại đây, Scene Class sẽ được chạy. Trong trường hợp sử dụng Scene Class thì chúng ta không cần thieetsghi vào những lệnh như [main_loop] như từ trước đến nay.
Khi định nghĩa 4 lệnh [init][quit][update][render] của Scene Class thì những lệnh đó được tự động gọi ra. Chúng ta không cần định nghĩa những lệnh không cần thiết. Tại [LogoScene] thì chỉ có lệnh [init] và [render] là được định nghĩa.
Thay đổi cảnh
Tiếp theo chúng ta sẽ thêm [Hình ảnh title] như một hình ảnh mới vào. Để biểu thị [Hình ảnh Title] thì chúng ta định nghĩa [Title Scene].
class TitleScene<Scene::Base
end
Hiện tại thì nội dung trong TitleScene là trống rỗng. Trước khi viết chế tạo nội dung của TitleScene thì chúng ta cần phải hiểu cách xử lý để chuyển từ [LogoScene] sang [TitleScene]. Để phát sinh sự chuyển cảnh thì trong chương trình chúng ta cần phải có lệnh dưới đây.
self.next_scene = Tên Scene Class
Dùng [self.next_scene] rồi đưa thêm tên class thì chúng ta sẽ chuyển được sang màn hình khác.
Chúng ta cùng thêm vào xử lý di chuyển từ màn hình "LoGoScene" đến "TitleScene".
class LogoScene < Scene::Base
def init
@logo = Image.new("Image/logo.png")
add_event(:key_down){go_next_scene}
add_event(mouse_button_down){so_nexr_scene}
end
def go_next_scene
self. next_scene = TitleScene.
end
(lược)
Khi chúng ta thêm tính năng mới [go_next_scene] thì công việc chuyển đổi cảnh được diễn ra. Việc gọi [go_next_scene] được lưu như một sự kiện. Một phím nào được nhấn trên bàn phím hay con trỏ chuột được nhấn xuống thì [go_next_scene] sẽ được gọi và màn hình sẽ chuyển sang [TitleScene]. Tại [TitleScene] thì chưa có gì được thực hiện. Khi chyển sang màn hình của tên game thì hình ảnh Logo biến mất và trên màn hình không có gì được vẽ cả.
Hình 5-12 Màn hình logo sau khi click chuột hay nhấn phím trên bàn phím thì màn hình sẽ được chuyển
Dưới đây là chương trình để chuyển màn hình.
punya01.rb
require 'mygame/boot'
class LogoScene < Scene::Base
def init
@logo = Image.new("logo.png")
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = TitleScene
end
def render
@logo.render
end
end
class TitleScene < Scene::Base
end
Scene.main_loop LogoScene
Màn hình tên game
Hình 5-13 Màn hình Title
Chúng ta cùng làm lên nội dung của màn hình Title bằng chương trình dưới đây.
class TitleScene<Scene::Base
def init
@bg = Image.new("images/bg_title.png")
@press = TransparentImage.new("images/press_any_key.png", :x => 80, :y => 270)
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene}
end
def go_next_scene
self.next_scene = MenuScene
end
def render
@bg.render
@press.render if frame_counter/16%4!=0
end
end
class MenuScene<Scene::Base
end
Lệnh [init] đã tạo hình ảnh hiển thị hình ảnh Title. Hiển ảnh vẽ bằng hình ảnh Title có 2 thứ
- Images/bg_title.png ...... Hình ảnh phông nền cho hình ảnh Title
- images/bg/press_any_button.png ..... Hình ảnh [PRESS ANY BUTTON]
[PRESS ANY BUTTON] được thay thế vào biến số [@press] và là biến số instance. Để xử lsy vẽ nên hình ảnh [@press] thì chúng ta thực hiện như chương trình dưới đây.
@press.render if frame_counter / 16%4 != 0
[frame_counter] là lệnh mà Scene Class mang theo khi hình ảnh đó được trả lại theo giá trị số frame(số lần loop) từ khi khởi động cho đến khi được thực hiện. Có nghĩa là [frame_counter] sẽ trả lại giá trị [0,1,2,...] giá trị đó được cộng vào trong mỗi lần thực hiện loop. Tại đây, [frame_counter / 16%4] khi không phải là [0] thì hình ảnh sẽ hiện ra nên chúng ta có thể nhìn thấy hình ảnh nhấp nháy. Nếu khi nhấn phím nào đó trên bàn phím hay nhấn và con chuột thì màn hình sẽ được chuyển sang màn hình [MenuScene] (hiện đang rỗng). Chương trình cho đến phần này là
punya02.rb
require 'mygame/boot'
class LogoScene < Scene::Base
def init
@logo = Image.new("images/logo.png")
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = TitleScene
end
def render
@logo.render
end
end
class TitleScene < Scene::Base
def init
@bg = Image.new("images/bg_title.png")
@press = TransparentImage.new("images/press_any_key.png", :x => 80, :y => 270)
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = MenuScene
end
def render
@bg.render
@press.render if frame_counter / 16 % 4 != 0
end
end
class MenuScene < Scene::Base
end
Scene.main_loop LogoScene
Fade-in và Fade-out
Khi chuyển màn hình mà chúng ta cho xử lý fade-in hoặc fade-out thì việc di chuyển màn hình sẽ trở nên nhẹ nhàng hơn.
Chúng ta thử Fade-in hình ảnh phông nền đằng sau của màn hình Title. Chúng ta thêm vào lệnh [render] ở [TitleScene] những xử lý như sau
def render
alpha = frame_counter * 8
alpha = 255 if alpha > 255
@bg.alpha = alpha
@bg.render
(lược)
[@bg] là object chỉ hình ảnh phông nền đằng sau. Ta cho giá trị [alpha] của hình ảnh thay đổi để thực hiện xử lý fade-in.
@bg.alpha = alpha
[alpha] thì ta sử dụng [frame_counter] để thay giá trị từ 0~255. Đầu tiên giá trị [alpha] là 0 nên màn hình tối đên hoàn toàn. Khi giá trị [alpha] đần đến [255] thì hình ảnh dần hiện lên và chúng ta có thể nhìn thấy rõ hình ảnh được.
Như vậy chúng ta cũng thêm xử lý fade-in với fade-out với hình ảnh Logo ban đầu. Tại [LogoScene] chúng ta thêm xử lý.
class LogoScene < Scene::Base
FADE_IN_TIME = 64
(lược)
def render
alpha = 0
if frame_counter <= FADE_IN_TIME
alpha = frame_counter * 255 / FADE_IN_TIME
elsif frame_counter <= FADE_IN_TIME * 4
alpha = 255
elsif frame_counter <= FADE_IN_TIME * 5
ct = frame_counter - FADE_IN_TIME * 4
alpha = 255 - ct * 255 / FADE_IN_TIME
end
@logo.alpha = alpha
@logo.render
end
end
[FADE_IN_TIME] là một định số và được thêm giá trị 64. Tại đây giá trị alpha được thay thế bởi biến [@logo.alpha] sẽ biến đổi như sau.
0->255 .... fade-in, hình ảnh dần được nhìn thấy rõ(trong khoảng frame 64)
|
255 ... chờ trong khoảng thời gian nhất định, hình ảnh được hiển thị (trong | khoảng 64*3)
255->0 .... fade-out. Hình ảnh sẽ dần dần biến mất (trong khoảng 64)
Hơn nữa chúng ta cũng có thể cho thêm tính năng nếu hình ảnh Logo biến mất hoàn toàn thì chương trình sẽ tự động chuyển sang hình ảnh tiếp bằng cách định nghĩa lệnh [update] trong [LogoScene] class như sau
def updater
go_next_scene if frame_counter >= FADE_IN_TIME *6
end
Tại đây nếu [fram_counter] đến giá trị trên (FADE_IN_TIME)*6 thì lệnh [go_next_scene] được gọi ra về màn hình Title sẽ được thay thế vào. Chương trình cho đến phần này là.
punyamenu03.rb
require 'mygame/boot'
class LogoScene < Scene::Base
FADE_IN_TIME = 64
def init
@logo = Image.new("images/logo.png")
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = TitleScene
end
def update
go_next_scene if frame_counter >= FADE_IN_TIME * 6
end
def render
alpha = 0
if frame_counter <= FADE_IN_TIME
alpha = frame_counter * 255 / FADE_IN_TIME
elsif frame_counter <= FADE_IN_TIME * 4
alpha = 255
elsif frame_counter <= FADE_IN_TIME * 5
ct = frame_counter - FADE_IN_TIME * 4
alpha = 255 - ct * 255 / FADE_IN_TIME
end
@logo.alpha = alpha
@logo.render
end
end
class TitleScene < Scene::Base
def init
@bg = Image.new("images/bg_title.png")
@press = TransparentImage.new("images/press_any_key.png", :x => 80, :y => 270)
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = MenuScene
end
def render
alpha = frame_counter * 8
alpha = 255 if alpha > 255
@bg.alpha = alpha
@bg.render
@press.render if frame_counter / 16 % 4 != 0
end
end
class MenuScene < Scene::Base
end
Scene.main_loop LogoScene
Định số
Định số là biến số mà tên bắt đầu từ chữ Alphabet được viết hoa. Định số khác với những biến số khác đó chính là chúng có thể được dùng trong trường hợp giá trị thay thế không thay đổi. Giá trị sẽ được thay đổi nếu chúng ta nhập lại một lần nữa nhưng lúc đó sẽ có tin nhắn thông báo hiện ra. Định số không được định nghĩa trong lệnh nên chúng ta nên chú ý.
class A
HELLO = "hello"
def foo
puts HELLO
end
end
Định số được định nghĩa trong class thì chúng ta có thể sử dụng trong class, nhưng nếu ta muốn sử dụng ngoài class thì phải có thêm [tên class::] đằng trước.
puts A::HELLO
Vẽ hình sử dụng Title
Tiếp theo chúng ta sẽ tạo nên màn hình menu. Chúng ta sẽ sử dụng [menu_tile.png] và [menu_title.png] để hiển thị màn hình menu có phông nền đằng sau.
Hình 5-16 [menu_tile.png] và [menu_title.png]
[menu_tile.png] có kích thước 128*128 pixel. Chúng ta sẽ trải hình ảnh này lên toàn màn hình, và trên đó sẽ là 1 hình ảnh [menu_title.png].
Hình 5-17 Màn hình Menu
Hơn nữa, chúng ta sẽ làm màn hình đằng sau chuyển động. Làm màn hình đằng sau chuyển động sẽ làm màn hình cũng thay đổi liên tục, dễ bắt mắt hơn. Đây là thủ thuật rất hay được sử dụng trên thị trường game.
Để dịch chuyển được hình ảnh phông đằng sau thì chúng ta phải chạy chương trình sau trong [MenuScene] class.
class MenuScene<Scene::Base
def init
@bg_tiles = Array.new(30) {Image.new("images/menu_tile.png")}
@title = TransparentImage.new("image/menu_title.png", :x => 192, :y => 16)
end
def update
@bg_tiles.each_with_index do |e,i|
e.x = e.w * (i%6) - frame_counter % e.w
e.y = e.h *(i/6) - frame_counter % e.h
end
end
def render
@bg_tiles.each {|e|e.render}
@title.render
end
end
Trong lệnh [init] thì sẽ sinh ra cùng 1 lúc 30 tấm dùng để làm nền cho hình ảnh menu, dãy [@bg_tiles] được đưa vào.
Lệnh [update] tiến hành xử lý cuộn từng hình ảnh hình nền. Xử lý cuộn thực hiện bằng cách làm chệch từng chút tọa độ của hình ảnh. Nếu chỉ lấy hình ảnh phủ lên thì đoạn code sau cũng có thể làm được.
def update
@bg_tiles.each_with_index do |e,i|
e.x = e.w *(i%6)
e.y = e.h *(i/6)
end
end
[e] là object mà hình ảnh được điền vào. [e.h][e.w] chính là kích thước của hình ảnh, sử dụng [i] đề tính toán tọa độ cần đặt theo thứ tự từ phía bên trên bên trái. Hơn nữa sử dụng [frame_counter] để làm chệch tất cả các hình ảnh nền đằng sau từng 1 pixel theo thứ tự từ phía bên trên bên trái.
def update
@bg_tiles.each_with_index do |e,i|
e.x = e.w*(i%6) - frame_counter % e.w
e.y = e.h*(i/6) - frame_counter % e.h
end
end
Chương trình cho đến phần này sẽ là
punyamenu04.rb
require 'mygame/boot'
class LogoScene < Scene::Base
FADE_IN_TIME = 64
def init
@logo = Image.new("images/logo.png")
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = TitleScene
end
def update
go_next_scene if frame_counter >= FADE_IN_TIME * 6
end
def render
alpha = 0
if frame_counter <= FADE_IN_TIME
alpha = frame_counter * 255 / FADE_IN_TIME
elsif frame_counter <= FADE_IN_TIME * 4
alpha = 255
elsif frame_counter <= FADE_IN_TIME * 5
ct = frame_counter - FADE_IN_TIME * 4
alpha = 255 - ct * 255 / FADE_IN_TIME
end
@logo.alpha = alpha
@logo.render
end
end
class TitleScene < Scene::Base
def init
@bg = Image.new("images/bg_title.png")
@press = TransparentImage.new("images/press_any_key.png", :x => 80, :y => 270)
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = MenuScene
end
def render
alpha = frame_counter * 8
alpha = 255 if alpha > 255
@bg.alpha = alpha
@bg.render
@press.render if frame_counter / 16 % 4 != 0
end
end
Skip xử lý hình ảnh
Tại máy tính có cấu hình thấp, khi tổ chức xử lý hình ảnh thì có nguy cơ phát sinh drop xử lý giữa chừng. Những Scene Class tiếp nhận bằng [Scene::Base] có tính năng skip hình ảnh để không làm drop chương trình giữa chừng.
Nếu nhấn phím [Page Down] thì hình ảnh sẽ được skip và FPS sẽ hạ xuống. Trong trường hợp FPS được cài mặc định 60, thì khi nhấn phím [Page Down] 1 lần thì FPS hạ xuống giá tị 30, nhấn lần thì 2 thì FPS được tái cài đặt với gái trị 20, nhấn phím [Page Up] thì giá trị FPS lại trở về giá trị ban đầu.
Mặt khác, với lệnh [render] của Scene Class, nếu ta thêm xử lý dưới đây thì giá trị FPS được cài đặt và giá trị FPS thực tế sẽ được hiển thị trên màn hình một cách dễ dàng.
def render
(lược)
ShadowFont.render("FPS:#{real_fps()}/#{fps()}")
end
Vẽ màn hình menu
Để vẽ màn hình menu thì chúng ta sử dụng những hình ảnh sau.
Để lựa chọn
Khi không được lựa chọn
Có 3 loại menu những mỗi loại lại được chuẩn bị những hình ảnh sáng và tối của chúng. Menu được chọn mà active thì sẽ hiển thị hình ảnh sáng, còn những menu còn lại thì hiển thị hình ảnh màu tối.
Để hình thành xử lý hình ảnh như trên thì chúng ta thêm những lệnh sau vào [MenuScene].
def init
(lược)
@menu = []
@back_menu = []
3.times do |i|
x = 70
y = 128 + i*100
img = TransparentImage.new("images/menu#{i}.png)", :x => x, :y => y)
back_img = TransparentImage.new("images/menu#{i}_back.png", :x => x, :y => y)
@menus << img
@back_menu << back_img
end
end
(lược)
def render
(@bg_tiles + @back_menus + @menus).each{|e| e.render}
@title.render
end
[@menus] và [@back_menus] là những dãy Object hình ảnh. [@menu] là nhận những hình ảnh sáng màu còn [@back_menus] nhận những hình ảnh tối màu. Xử lý hình ảnh [@menus] và [@back_menus] được thêm vào lệnh [render]. Tại đây có sự liên kết giữa 3 dãy hình ảnh và được gọi ra và thực hiện bằng lệnh [each].
(@bg_tiles + @back_menus + @menus).each{|e| e.render}
Như trên, chúng ta dùng [+] để liên kết giữa các dãy rồi thực hiện lệnh [render] đối với những object chứa 3 dãy trên. Thứ tự kết nối này đều có ý nghĩa. Vì lệnh [each] sẽ thực hiện lệnh đối với object theo thứ tự từ đầu dãy đến cuối nên theo từng thứ tự chúng ta sắp xếp trong dãy mà thứ tự hiển thị hình ảnh cũng khã nhau. Object chứa[@menu]được viết ra ở cuối cùng nên chúng ta nhìn thấy cả 3 hình ảnh sáng. Chương trình cho đến đây là
require 'mygame/boot'
class LogoScene < Scene::Base
FADE_IN_TIME = 64
def init
@logo = Image.new("images/logo.png")
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = TitleScene
end
def update
go_next_scene if frame_counter >= FADE_IN_TIME * 6
end
def render
alpha = 0
if frame_counter <= FADE_IN_TIME
alpha = frame_counter * 255 / FADE_IN_TIME
elsif frame_counter <= FADE_IN_TIME * 4
alpha = 255
elsif frame_counter <= FADE_IN_TIME * 5
ct = frame_counter - FADE_IN_TIME * 4
alpha = 255 - ct * 255 / FADE_IN_TIME
end
@logo.alpha = alpha
@logo.render
end
end
class TitleScene < Scene::Base
def init
@bg = Image.new("images/bg_title.png")
@press = TransparentImage.new("images/press_any_key.png", :x => 80, :y => 270)
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = MenuScene
end
def render
alpha = frame_counter * 8
alpha = 255 if alpha > 255
@bg.alpha = alpha
@bg.render
@press.render if frame_counter / 16 % 4 != 0
end
end
class MenuScene < Scene::Base
def init
@bg_tiles = Array.new(30) { Image.new("images/menu_tile.png") }
@title = TransparentImage.new("images/menu_title.png", :x => 192, :y => 16)
@menus = []
@back_menus = []
3.times do |i|
x = 70
y = 128 + i * 100
img = TransparentImage.new("images/menu#{i}.png", :x => x, :y => y)
back_img = TransparentImage.new("images/menu#{i}_back.png", :x => x, :y => y)
@menus << img
@back_menus << back_img
end
end
def update
@bg_tiles.each_with_index do |e, i|
e.x = e.w * (i % 6) - frame_counter % e.w
e.y = e.h * (i / 6) - frame_counter % e.h
end
end
def render
(@bg_tiles + @back_menus + @menus).each {|e| e.render }
@title.render
end
end
Scene.main_loop LogoScene
Chọn menu
Chúng ta cùng tạo nên tao tác sau thôi, Object được chọn sẽ hiển thị hình sáng còn những object còn lại không được chọn sẽ hiển thị màu tối.
def init
(lược)
@cursor_id = 0
3.times do |i|
(lược)
add_event(:mouse_motion){|event| @cursor_id = i if back_img.hit?(event)}
end
end
(lược)
def update
(lược)
@menu.each_with_index {|img,i| img.hide = @cursor_id !=i}
end
Biến số cursor_id được đưa là tín hiệu kệnh menu được chọn. Hãy nhìn xử lý được thêm vào lệnh [update].
@menus.each_with_index {|img,i| img.hide = @cursor_id != i}
[@menus] chính là dãy chỉ object hình ảnh kênh menu. Sử dụng [each_with_index] để tiến hành xử lý tiếp theo đối với từng thành tố.
img.hide = @cursor_id != i
[@cursor_id] được trả về câu trả lời true hay false cho so sánh [@cursor_id] với [id]. Vì chúng ta so sánh bằng [!=] nên nếu giá trị không bằng thì sẽ trả lại giá trị true. Tức là [cursor_id] chỉ khi có giá trị bằng chỉ số của các thành tố thì [img.hide] sẽ được trả lại giá trị false, hình ảnh đó sẽ được hiển thị. Ngoài ra, khi [img.hide] được trả giá trị false thì chúng ta sẽ nhìn thấy hình ảnh bị giấu đi.
Tại lệnh [init] thì chúng ta cho giá trị ban đầu của [@cursor_id] giá trị 0. Để thay thế giá trị của [@cursor_id] thì chúng ta có dòng lệnh dưới đây.
3.times do |i|
(lược)
add_event(:mouse_motion){|event| @cursor_id = i if back_img.hit?(event)}
end
[add_event(:mouse_motion)] là event phát sinh khi con trỏ chuột được di chuyển.
Bộ phận tiếp theo phán đoán xem tọa độ của con trỏ chuột và tọa độ của thanh menu có trùng nhau hay không.
back_img.hit?(event)
[hit?] là lệnh phán đoán xem tọa độ (x,y) của object đối với object hình ảnh có trùng nhau hay không. Lệnh [hit?] được trao trong event của con trỏ chuột nên nó sẽ phán đoán trên hình có con trỏ chuột hay không.
Như lệnh dưới đây, khi con trỏ chuột có ở trên thanh menu thì chỉ số của thanh menu đó sẽ được thay thế vào giá trị [@cursor_id].
@cursor_id = i if back_imd.hit?(event)
Như vậy nếu chúng ta di chuyển con trỏ trên thanh menu nào thì thanh menu đó nhìn thấy sáng hơn.
Hình 5-20 Di chuyển con chuột trên thanh menu nào thì thanh menu đó sẽ có màu nhìn sáng hơn
Chương trình cho đến phần này là
require 'mygame/boot'
class LogoScene < Scene::Base
FADE_IN_TIME = 64
def init
@logo = Image.new("images/logo.png")
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = TitleScene
end
def update
go_next_scene if frame_counter >= FADE_IN_TIME * 6
end
def render
alpha = 0
if frame_counter <= FADE_IN_TIME
alpha = frame_counter * 255 / FADE_IN_TIME
elsif frame_counter <= FADE_IN_TIME * 4
alpha = 255
elsif frame_counter <= FADE_IN_TIME * 5
ct = frame_counter - FADE_IN_TIME * 4
alpha = 255 - ct * 255 / FADE_IN_TIME
end
@logo.alpha = alpha
@logo.render
end
end
class TitleScene < Scene::Base
def init
@bg = Image.new("images/bg_title.png")
@press = TransparentImage.new("images/press_any_key.png", :x => 80, :y => 270)
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = MenuScene
end
def render
alpha = frame_counter * 8
alpha = 255 if alpha > 255
@bg.alpha = alpha
@bg.render
@press.render if frame_counter / 16 % 4 != 0
end
end
class MenuScene < Scene::Base
def init
@bg_tiles = Array.new(30) { Image.new("images/menu_tile.png") }
@title = TransparentImage.new("images/menu_title.png", :x => 192, :y => 16)
@menus = []
@back_menus = []
@cursor_id = 0
3.times do |i|
x = 70
y = 128 + i * 100
img = TransparentImage.new("images/menu#{i}.png", :x => x, :y => y)
back_img = TransparentImage.new("images/menu#{i}_back.png", :x => x, :y => y)
@menus << img
@back_menus << back_img
add_event(:mouse_motion) {|event| @cursor_id = i if back_img.hit?(event) }
end
end
def update
@bg_tiles.each_with_index do |e, i|
e.x = e.w * (i % 6) - frame_counter % e.w
e.y = e.h * (i / 6) - frame_counter % e.h
end
@menus.each_with_index {|img, i| img.hide = @cursor_id != i }
end
def render
(@bg_tiles + @back_menus + @menus).each {|e| e.render }
@title.render
end
end
Scene.main_loop LogoScene
Quyết định menu
Chúng ta cùng thêm vào xử lý di chuyển màn hình khi đã nhấn trên thanh menu.
def init
(lược)
add_event(:mouse_button_down){go_next_scene}
end
def go_next_scene
case @cursor_id
when 0
self.next_scene = PunnyRaceScene
when 1
self.next_scene = CharacterScene
when 2
self.next_scene = Title Scene
end
end
Con trỏ chuột được nhấn thì lệnh [go_next_scene] sẽ được gọi ra và lưu trong lệnh [init]. [go_next_scene] sẽ thông qua [@cursor_id] để phân thành các trường hộ màn hình như sau.
Dầu tiên chúng ta phải tạo màn hình.
class CharacterScene < Scene::Base
end
classPunyRaceScene < Scene::Base
end
Nội dung trong đây vẫn chưa có thì nên vẫn trốn không, chúng ta có thể xác nhận được màn hình đẫ được thay đổi nội dung chưa. Nội dung cho đế phần này có trong lệnh dưới đây
class LogoScene < Scene::Base
FADE_IN_TIME = 64
def init
@logo = Image.new("images/logo.png")
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = TitleScene
end
def update
go_next_scene if frame_counter >= FADE_IN_TIME * 6
end
def render
alpha = 0
if frame_counter <= FADE_IN_TIME
alpha = frame_counter * 255 / FADE_IN_TIME
elsif frame_counter <= FADE_IN_TIME * 4
alpha = 255
elsif frame_counter <= FADE_IN_TIME * 5
ct = frame_counter - FADE_IN_TIME * 4
alpha = 255 - ct * 255 / FADE_IN_TIME
end
@logo.alpha = alpha
@logo.render
end
end
class TitleScene < Scene::Base
def init
@bg = Image.new("images/bg_title.png")
@press = TransparentImage.new("images/press_any_key.png", :x => 80, :y => 270)
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = MenuScene
end
def render
alpha = frame_counter * 8
alpha = 255 if alpha > 255
@bg.alpha = alpha
@bg.render
@press.render if frame_counter / 16 % 4 != 0
end
end
class MenuScene < Scene::Base
def init
@bg_tiles = Array.new(30) { Image.new("images/menu_tile.png") }
@title = TransparentImage.new("images/menu_title.png", :x => 192, :y => 16)
@menus = []
@back_menus = []
@cursor_id = 0
3.times do |i|
x = 70
y = 128 + i * 100
img = TransparentImage.new("images/menu#{i}.png", :x => x, :y => y)
back_img = TransparentImage.new("images/menu#{i}_back.png", :x => x, :y => y)
@menus << img
@back_menus << back_img
add_event(:mouse_motion) {|event| @cursor_id = i if back_img.hit?(event) }
end
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
case @cursor_id
when 0
self.next_scene = PunyRaceScene
when 1
self.next_scene = CharactersScene
else
self.next_scene = TitleScene
end
end
def update
@bg_tiles.each_with_index do |e, i|
e.x = e.w * (i % 6) - frame_counter % e.w
e.y = e.h * (i / 6) - frame_counter % e.h
end
@menus.each_with_index {|img, i| img.hide = @cursor_id != i }
end
def render
(@bg_tiles + @back_menus + @menus).each {|e| e.render }
@title.render
end
end
class CharactersScene < Scene::Base
end
class PunyRaceScene < Scene::Base
end
Scene.main_loop LogoScene
Màn hình giới thiệu nhân vật
class CharactersScene < Scene::Base
def init
@bg = Image.new("images/bg_characters.png")
add_event(:key_down) {self.next_scene = MenuScene}
add_event(:mouse_button_down){self.next_scene = MenuScene}
end
def render
alpha = frame_counter * 32
alpha = 255 if alpha > 255
@bg.alpha = alpha
@bg.render
end
Hình 5-21 Màn hình giới thiệu nhân vật
Trong màn hình giới thiệu nhân vật, hình ảnh [bg_characters.png] sẽ được hiển thị như phông nền đằng sau. Cũng giống như màn hình logo, sẽ diễn ra xử lsy fade-in đối với hình ảnh phông nền. Nếu nhấn một nút nào đó trên bàn phím hay nhấn con trỏ chuột thì sẽ chuyển về màn hình menu.
Chương trình cho đến phần này sẽ là
punyamenu08.rb
require 'mygame/boot'
class LogoScene < Scene::Base
FADE_IN_TIME = 64
def init
@logo = Image.new("images/logo.png")
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = TitleScene
end
def update
go_next_scene if frame_counter >= FADE_IN_TIME * 6
end
def render
alpha = 0
if frame_counter <= FADE_IN_TIME
alpha = frame_counter * 255 / FADE_IN_TIME
elsif frame_counter <= FADE_IN_TIME * 4
alpha = 255
elsif frame_counter <= FADE_IN_TIME * 5
ct = frame_counter - FADE_IN_TIME * 4
alpha = 255 - ct * 255 / FADE_IN_TIME
end
@logo.alpha = alpha
@logo.render
end
end
class TitleScene < Scene::Base
def init
@bg = Image.new("images/bg_title.png")
@press = TransparentImage.new("images/press_any_key.png", :x => 80, :y => 270)
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = MenuScene
end
def render
alpha = frame_counter * 8
alpha = 255 if alpha > 255
@bg.alpha = alpha
@bg.render
@press.render if frame_counter / 16 % 4 != 0
end
end
class MenuScene < Scene::Base
def init
@bg_tiles = Array.new(30) { Image.new("images/menu_tile.png") }
@title = TransparentImage.new("images/menu_title.png", :x => 192, :y => 16)
@menus = []
@back_menus = []
@cursor_id = 0
3.times do |i|
x = 70
y = 128 + i * 100
img = TransparentImage.new("images/menu#{i}.png", :x => x, :y => y)
back_img = TransparentImage.new("images/menu#{i}_back.png", :x => x, :y => y)
@menus << img
@back_menus << back_img
add_event(:mouse_motion) {|event| @cursor_id = i if back_img.hit?(event) }
end
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
case @cursor_id
when 0
self.next_scene = PunyRaceScene
when 1
self.next_scene = CharactersScene
else
self.next_scene = TitleScene
end
end
def update
@bg_tiles.each_with_index do |e, i|
e.x = e.w * (i % 6) - frame_counter % e.w
e.y = e.h * (i / 6) - frame_counter % e.h
end
@menus.each_with_index {|img, i| img.hide = @cursor_id != i }
end
def render
(@bg_tiles + @back_menus + @menus).each {|e| e.render }
@title.render
end
end
class CharactersScene < Scene::Base
def init
@bg = Image.new("images/bg_characters.png")
add_event(:key_down) {self.next_scene = MenuScene}
add_event(:mouse_button_down){self.next_scene = MenuScene}
end
def render
alpha = frame_counter * 32
alpha = 255 if alpha > 255
@bg.alpha = alpha
@bg.render
end
class PunyRaceScene < Scene::Base
end
Scene.main_loop LogoScene
Màn hình hóa cuộc đua của các punya
Trong chương trước, chúng ta đã viết chương trình để cho các con punya thi đấu, bây giờ chúng ta sẽ sử dụng nó như một màn hình của chương trình.
class Punya
(lược)
end
class PunyRaceScene < Scene::Base
def init
@bg = Image.new("images/bg_race.png")
@punyas = Array.new(6) {|i| Punya.new(i)}
add_event(:key_down) {self.next_scene = MenuScene}
add_event(:mouse_button_down) {self.next_scene = MenuScene}
end
def update
@punyas.each {|e| e.update}
end
def render
@bg.render
@punya.each {|e| e.render}
end
end
Class [PunyRaceScene] sẽ là class thực hiện [Cuộc đua của các Punya]. Nếu có bất cứ phím nào trên bàn phím hay con trỏ chuột được nhấn thì màn hình sẽ trở về màn hình Menu ban đầu.
Hình 5-22 Cuộc đua của Punya(2)
Đến đây thì chương trình đã hoàn thành. Chúng ta cũng đã làm màn hình hóa được cuộc đua của các punya và đã dễ dàng kết hợp nó với những màn hình khác. Chương trình hoàn thành là.
punyamenu.rb
require 'mygame/boot'
class LogoScene < Scene::Base
FADE_IN_TIME = 64
def init
@logo = Image.new("images/logo.png")
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = TitleScene
end
def update
go_next_scene if frame_counter >= FADE_IN_TIME * 6
end
def render
alpha = 0
if frame_counter <= FADE_IN_TIME
alpha = frame_counter * 255 / FADE_IN_TIME
elsif frame_counter <= FADE_IN_TIME * 4
alpha = 255
elsif frame_counter <= FADE_IN_TIME * 5
ct = frame_counter - FADE_IN_TIME * 4
alpha = 255 - ct * 255 / FADE_IN_TIME
end
@logo.alpha = alpha
@logo.render
end
end
class TitleScene < Scene::Base
def init
@bg = Image.new("images/bg_title.png")
@press = TransparentImage.new("images/press_any_key.png", :x => 80, :y => 270)
add_event(:key_down) { go_next_scene }
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
self.next_scene = MenuScene
end
def render
alpha = frame_counter * 8
alpha = 255 if alpha > 255
@bg.alpha = alpha
@bg.render
@press.render if frame_counter / 16 % 4 != 0
end
end
class MenuScene < Scene::Base
def init
@bg_tiles = Array.new(30) { Image.new("images/menu_tile.png") }
@title = TransparentImage.new("images/menu_title.png", :x => 192, :y => 16)
@menus = []
@back_menus = []
@cursor_id = 0
3.times do |i|
x = 70
y = 128 + i * 100
img = TransparentImage.new("images/menu#{i}.png", :x => x, :y => y)
back_img = TransparentImage.new("images/menu#{i}_back.png", :x => x, :y => y)
@menus << img
@back_menus << back_img
add_event(:mouse_motion) {|event| @cursor_id = i if back_img.hit?(event) }
end
add_event(:mouse_button_down) { go_next_scene }
end
def go_next_scene
case @cursor_id
when 0
self.next_scene = PunyRaceScene
when 1
self.next_scene = CharactersScene
else
self.next_scene = TitleScene
end
end
def update
@bg_tiles.each_with_index do |e, i|
e.x = e.w * (i % 6) - frame_counter % e.w
e.y = e.h * (i / 6) - frame_counter % e.h
end
@cursor_id -= 1 if new_key_pressed?(Key::UP)
@cursor_id += 1 if new_key_pressed?(Key::DOWN)
@cursor_id %= @menus.size
@menus.each_with_index {|img, i| img.hide = @cursor_id != i }
go_next_scene if new_key_pressed?(Key::X)
end
def render
(@bg_tiles + @back_menus + @menus).each {|e| e.render }
@title.render
end
end
class CharactersScene < Scene::Base
def init
@bg = Image.new("images/bg_characters.png")
add_event(:key_down) { self.next_scene = MenuScene }
add_event(:mouse_button_down) { self.next_scene = MenuScene }
end
def render
alpha = frame_counter * 32
alpha = 255 if alpha > 255
@bg.alpha = alpha
@bg.render
end
end
class Punya
def initialize(idx)
@image = TransparentImage.new("images/punya#{idx}.png")
@image.x = 20
@image.y = 20 + idx * 70
@image.w = 64
@image.h = 64
animations = {
:run => [8, [0, 1, 2, 0]],
:win => [8, [3, 4, 5, 4]],
}
@image.add_animation(animations)
@image.start_animation(:run)
end
def update
@image.update
if goal?
@image.start_animation(:win)
else
@image.x += rand(4)
end
end
def goal?
@image.x > 560
end
def render
@image.render
end
end
class PunyRaceScene < Scene::Base
def init
@bg = Image.new("images/bg_race.png")
@punyas = Array.new(6) {|i| Punya.new(i) }
add_event(:key_down) { self.next_scene = MenuScene }
add_event(:mouse_button_down) { self.next_scene = MenuScene }
end
def update
@punyas.each {|e| e.update }
end
def render
@bg.render
@punyas.each {|e| e.render }
end
end
Scene.main_loop LogoScene
All rights reserved