5-1 Cuộc chạy đua của những Punya(1)

5-1 Cuộc chạy đua của những Punya(1)

Từ đầu đến giờ cũng ta đã được giải thích những thứ chỉ liên quan đến lý thuyết, bây giờ chúng ta sẽ chính thức bắt đầu thử sức với game thực sự. Nói như vậy những chúng ta sẽ bắt đầu bằng thứ thông thường nhất. Tại phần này chúng ta sẽ tạo một game để các con punya chạy tên màn hình đua với nhau, được gọi là "Punya Race".

Nội dung game

Chúng ta hãy nghĩ xem những gì sẽ hiển thị trên màn hình.


  • Punya x 6
  • Phong cảnh màn sau

Những gì xuất hiện trên màn hình đó chính là 6 con punya và khung cảnh hình ảnh màn sau. Để thao tác trở nên đơn giản hơn thì 6 con punya mọi người không cần phải thao tác mà bọn chúng sẽ tự động di chuyển. Các con punya đồng loạt chạy về phía bên phải màn hình và tất cả các con punya chạy qua được vạch đích thì trò chơi sẽ kết thúc.

Hình 5-1 Punya Race

1.png

Hình ảnh punya

Chúng ta sẽ sử dụng những hình ảnh dưới đây.

Hình 5-2 Hình ảnh punya và phong cảnh nền

punya0.png

bg_race.png

Chúng ta tạo một folder để ảnh rồi để tất cả hình ảnh Punya và hình ảnh phông nền vào đó.

Hình 5-3 Tạo một folder để ảnh

2.PNG

Punya class

Nhân vật chính của game này là Punya. Các con punya được chế tạo từ Punya class và xuất hiện trong thế giới Game. Punya class có 2 chức năng chính.


  • Vẽ ảnh Punya
  • Chạy (Di chuyển về phía bên phải)

Đầu tiên chúng ta thử tạo Punya class trước đã nhé.

punya00.rb

require 'mygame/boot'

class Punya
  def initialize
    @image = TransparentImage.new("images/punya0.png")
    @image.x = 20
    @image.y = 20
    @image.w = 64
    @image.h = 64
  end

  def update
    @image.update
    @image.x += 2
  end

  def render
    @image.render
  end
end

punya = Punya.new
main_loop do
punya.update
punya.render
end

Chương trình này chúng ta có thể chia ra thành 2 bộ phận lớn khác nhau. Bộ phận đầu đó chính là định nghĩa Punya class còn bộ phận sau chính là bộ phận main_loop.

Đầu tiên chúng ta cùng xem xét kĩ Punya class. Trong Punya class tồn tại 3 lệnh chính mà chức năng của từng lệnh sẽ được giới thiệu dưới đây.


  • initialize .... Những xử lý ban đầu. Xác lập hình ảnh, tọa độ và kích thước hình ảnh
  • update ... Xử lý làm mới. Cộng thêm vào tọa độ và làm mới tọa độ hình ảnh
  • render ... Xử lý vẽ ảnh. Gọi hình ảnh ra màn hình

Lệnh [initialize] tạo nên object hình ảnh và thêm nó vào biến số @image. Ngay sau đó sẽ xác lập tọa độ ban đầu và kích thước của [@image]. Lệnh [update] là lệnh thực thi [@image.update] và sẽ thêm sử lý hoạt hình sau này. Nếu chúng ta không thực hiện [@image.update] thì biến đổi hoạt hình sẽ không xảy ra. Cùng mới đó,lệnh [update] trong Punya class sẽ xử lý di chuyển cho Punya. Cứ mỗi khi lệnh [update] được gọi thì tọa độ x của hình ảnh lại được thêm 2 đơn vị vào và vị trí của Punya được thay đổi.

Tạo nên object và main_loop

Để cho punya có thể chạy thực sự được thì nó phụ thuộc vào phần đằng sau này.

punya = Punya.new
main_loop do
  punya.update
  punya.render
end

Đến đây cũng không có gì đặc biệt khó cả. Dòng đầu tiên là dòng tạo ra object punya và thay nó vào biến số [punya].

punya = Punya.new

Trong main loop thì có 2 lệnh được gọi ra chính là [update] và lệnh [render] đối với punya.

  punya.update
  punya.render
Hình 5-4 Punya đang chạy về hướng bên phải màn hình

3.png

Nếu chạy chương trình này thì trên màn hình phía trên bên trái sẽ xuất hiệu một con Punya đang chuyển động về phía bên phải màn hình. Cho đến khi chương trình chưa được dừng lại thì sẽ coi như chúng ta đang gọi ra một chuỗi [update] vô hạn nên con punya này sẽ chạy luôn ra khỏi màn hình.

6 con Punya

Như vậy cho đến đây chúng ta đã tạo nên Punya class cho con Punya của mình và cũng dùng Punya class để làm hiện 1 con punya lên màn hình, cho chạy. Tiếp theo, chúng ta sẽ đồng thời làm 6 con punya chạy trên màn hình.

Vì chúng ta đã tạo lên Punya class nên việc tạo 6 con punya là một việc rất đơn giản. Trước khi tạo ra 6 con punya thì chúng ta cần sửa lại Punya class như sau.

class Punya
  def initialize(idx)
    @image = TransparentImage.new("images/punya#{idx}.png")
    @image.x = 20
    @image.y = 20 + idx*70
    (lược)
end

Argument [idx] của [initialize] chính là số của punya.[idx] nhận giá trị 0~5. Những bức tranh màu khác nhau gồm 6 loại từ 0~5 nên qua [idx] thì file png sẽ được gọi ra và hình ảnh cũng sẽ thay đổi.


  • idx = 0 ....images/punya0.png sẽ được đọc
  • idx = 1 ....images/punya1.png sẽ được đọc
  • idx = 2 ....images/punya2.png sẽ được đọc

Hơn nữa, tọa độ y cũng được thay đổi dựa trên sự thay đổi của [idx]


  • idx = 0 .... 20 + 0*70 -> 20
  • idx = 1 .... 20 + 1*70 -> 90
  • idx = 2 .... 20 + 2*20 -> 160

Tiếp theo, Object được sinh ra từ Punya class và phần loop sẽ được chỉnh sửa lại như sau.

punyas = Array.new(6) {|i| Punya.new{i}}
main_loop do
  punyas.each do |punya|
    punya.update
    punya.render
  end
end

Dòng đầu tiên là dòng để sinh ra những Object.

punyas = Array.new(6) {|i|} Punya.new(i)}

Bằng lệnh [Array.new(6)] thì chúng ta đã sinh ra một hàng có kích thước 6, và những object được sinh ra bởi [Punya.new|i|] sẽ là những thành tố trong dãy.|i| nhận giá trị 0~5 nên chúng ta sẽ có dãy với những thành tố [Punya0]~[Punya5].

Hình 5-5 6 con punya xếp thành hàng

4.png

Nếu chạy chương trình này, chúng ta sẽ có 6 con punya cùng một lúc chạy về phía bên phải màn hình.Tuy nhiên những con punya trên sẽ di chuyển với một tốc độ như nhau, như vậy không thú vị nên chúng ta sẽ sửa lệnh update lên Punya class.

  def update
    @image.update
    @image.x += rand(4)
  end

Như vậy tọa độ x sẽ được tính toán vào và chúng ta sử dụng số ngẫu nhiên. [rand(4)] sẽ trả lại câu trả lời là giá trị 0~3. Cứ mỗi lần lệnh [update] đối với tọa độ x của Punya thì tọa độ của chũng sẽ bị công 0~3.

Hình 5-6 Quảng đường di chuyển sẽ ngày càng có khoảng cách

5.png

Chương trình được chạy cho đến nay là punya01.rb

Phán quyết Goal

Tại đây chúng ta sẽ làm cho nếu Punya chạy đến đầu phải cuối cùng thì đó sẽ là goal và punya sẽ dừng lại. Làm thế nào để chúng ta phán định xem Punya đã về đến đích chưa? Chúng ta có thể làm như nếu [@image.x] có giá trị lớn hơn một giá trị nhất định nào đó thì sẽ coi như đã về đến đích. Tại đó, chúng ta thêm lệnh [goal?] vào Punya class.

  def goal?
    @image.x > 560
  end

Lệnh [goal?] trả lại kết quả nếu [@image.x] có giá trị lớn hơn [560] sẽ trả lại kết quả [True] còn nếu có giá trị nhỏ hơn [560] thì kết quả trả lại sẽ là [False]. Chúng ta sẽ thêm lệnh [goal?] để sửa lệnh [update].

  def update
    @image.update
  unless goals?
    @image.x += rand(4)
  end
end

Nếu [goal?] không đúng, có nghĩa rằng chỉ khi punya vẫn chưa về đích thì [@image.x] vẫn sẽ được cộng thêm giá trị. Nếu [goal?] trả lại giá trị đúng thì giá trị [@image.x] sẽ không được cộng thêm giá trị, và nếu [@image.x] vượt quá giá trị 560 thì sẽ không tiến thêm được nữa.

Chương trình cho đến hiện tại sẽ được viết trong script [punyarace02.rb].

Hiệu ứng hoạt hình

Bây giờ chúng ta sẽ thêm vào hiệu ứng hoạt hình. Bức hình dưới đây sẽ miêu tả hành động của punya.

Hình 5-7

punya0.png

Chúng ta xác lập cho hành động [:run] là (0)(1)(2) và xác lập hành động cho hành động khi đến vạch đích [:win] là (3)(4)(5).

Chúng ta chỉnh sửa lệnh [initialize] trong Punya class như sau.

def initialize
  (lược)
  @image.h = 64
  animations = {
    :run => [8, [0,1,2,0]],
    :win => [8, [3,4,5,3]],
  }
  @image.add_animation(animation)
  @image.start_animation(:run)
end

Chúng ta thiết lập hành động hoạt hình cho [@image] bằng [add_animation]. Hơn nữa, để hành động đầu tiên của punya là chạy thì chúng ta thực hiện lệnh [@image.start_animation(:run)].

Chúng ta cũng cần thực hiện thêm hành động khi punya chạy về đích thì hiệu ứng hoạt hình :win được khởi động. Vì vậy, chúng ta lại sửa lệnh update một chút

  def update
    @image.update
  if goal?
    @image.start_animation(:win)
  else
    @image.x += rand(4)
  end
end

Sau khi Punya đến đích thì [@image.start_animation(:win)] sẽ được thực hiện và cho đến khi về đến đích thì tọa độ x sẽ được cộng giá trị liên tục. Script về chương trình cho đến nay là [punyarace03.rb]

Hiển thị hình nền

Tiếp theo chúng ta chỉ cần thêm hình ảnh nền đằng sau thì chương trình sẽ được hoàn thành.

Hình 5-8 Hình nền [bg_race.png]

bg_race.png

bg = Image.new("image/bg_race.png")
punyas = Array.new(6) {|i|Punya.new(i)}
main_loop do
  bg.render
  punyas.each do |punya|
  (lược)

Chúng ta tạo Object hình ảnh phông nền rồi thay thế vào biến số [bg]. Tiếp theo trong loop, thực hiện lệnh [bg.render] để hiên thị phông nền đằng sau. Chúng ta cần để lệnh [bg.render] trước khi gọi hình ảnh của punya. Lý do cần làm thế là vì những thao tác trong loop sẽ được thực hành theo một thứ tự nhất định. Nếu chúng ta để hình ảnh punya trước rồi mới đè lên đó là phông nền thì hình ảnh punya sẽ bị đè lên và hình ảnh punya sẽ không nhìn thấy hình ảnh nữa.

Hình 5-9 Hình ảnh đã thêm cả phông nền đằng sau

6.png

Hoàn thành

Cho đến giờ thì chương trình đã hoàn thành. File nguồn chỉ gồm đúng 40 dòng, là một chương trình rất ngắn. Tuy nhiên, kĩ năng biến punya được sinh ra từ punya class và biến chúng trở thành một dãy là một kĩ năng rất quan trọng nên chúng ta nên chuyên nghiệp về kĩ năng này.

Cuối cùng, đây là chương trình hoàn thành.

punyarace.rb

require 'mygame/boot'

class Punya
  def initialize
    @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,3]],
    }
    @image.add_animation(animations)
    @image.start_animation(:run)
  end

  def update
    @image.update
    if goals?
      @image.start_animation(:win)
    else
      @image.x += rand(4)
    end
  end

  def goal?
    @image.x > 560
  end

  def render
    @image.render
  end
end

bg = Image.new("images/bg_race.png")
punyas = Array.new(6) {|i|Punya.new(i)}
main_loop do
  bg.render
  punyas.each do |punya|
    punya.update
    punya.render
  end
end