Trong chương trước, ta đã biết cách vẽ hình, làm cho chúng chuyển động, nhưng ta chưa hề biết tương tác với chúng, những đối tượng của chúng ta như trong các trò chơi điện tử (games). Trong các trò chơi này mình phải biết bấm (click), kéo (drag), di chuyển (move), đụng (hit) hay bắn bể (pop) đối tượng trên máy. Trong chương này chúng ta sẽ học cách làm những việc đó.
1) Đầu tiên học BẤM (CLICK) VÀ BẤM & KÉO để tương tác (click & drag)
a) BẤM (CLICK) ĐÂU VẼ ĐẤY
# ClickDots.py import pygame pygame.init() screen = pygame.display.set_mode([800,600]) pygame.display.set_caption("Click to draw") # 1: phụ đề cho cửa sổ keep_going = True # Boolean, đầu tiên cho trị là True RED = (255,0,0) # Bộ màu RGB cho màu đỏ (RED) radius = 15 while keep_going: # While loop của game. Chỉ ngưng khi keep_going=False. for event in pygame.event.get(): # Máy tìm xem sự kiện (events*) được đưa vào là gì if event.type == pygame.QUIT: # nếu event là bấm nút X đỏ trên hình thì cho: keep_going = False # để ngưng vòng lặp While if event.type == pygame.MOUSEBUTTONDOWN: # 2 spot = event.pos # 3 pygame.draw.circle(screen, RED, spot, radius) # 4 pygame.display.update() # Update display pygame.quit() # Exit
Chú thích: *sự kiện (event) là hoạt động của người sử dụng mà máy tính có thể nhận biết như click chuột, nhấn nút trên bàn phím (keypress), bấm giờ…
#1: hàm pygame.display.set_caption() cho ta tiêu đề của cửa sổ mà đối số là một string. Ở đây tiêu đề là: “Click to draw” (Bấm để vẽ. Tuỳ bạn cho tiêu đề thích hợp).
#2: hàm pygame.MOUSEBUTTONDOWN cho ta biết người sử dụng đã nhấn một nút trên chuột. Khi đó sự kiện ấy sẽ xuất hiện trong danh sách những sự kiện mà chương trình của ta nhận được từ hàm pygame.event.get().
#3: event.pos ghi vị trí toạ độ nơi ta bấm chuột vào một biến tên là spot
#4: hàm pygame.draw.circle(screen, RED, spot, radius): vẽ một vòng tròn trên màn hình sreen, màu RED, tại vị trí spot, có bán kính radius.Những arguments (đối số) trong hàm này đã được gán trị số ở phần trên
Run->Run Module chương trình này rồi bấm chuột để vẽ như hình9-1 dưới đây hay vẽ rồng rắn, voi, chuột…tuỳ ý các bạn.
b) BẤM VÀ KÉO LÊ (CLICK & DRAG) con chuột để vẽ
Chúng ta sẽ làm như cầm cây cọ vẽ thật vậy. Ta vừa bấm vừa kéo lê con chuột để vẽ 1 cách trơn tru, trôi chảy.
# DragDots.py import pygame pygame.init() screen = pygame.display.set_mode([800,600]) pygame.display.set_caption(“Click and drag to draw”) keep_going = True # Boolean variable YELLOW = (255,255,0) # bộ màu RGB cho màu vàng YELLOW radius = 5 # bán kính vòng tròn là 5 pixels mousedown = False # Boolean variable while keep_going: # Game loop for event in pygame.event.get(): # máy xem sự kiện (events) được đưa vào là gì if event.type == pygame.QUIT: # nếu ta click chữ X đỏ ở góc trên cùng hình vẽ thì: keep_going = False # để thôi, dứt vòng lặp While if event.type == pygame.MOUSEBUTTONDOWN: # nếu loại event là nhấn chuột thì: mousedown = True if event.type == pygame.MOUSEBUTTONUP: # nếu loại event là buông chuột (release) thì: mousedown = False if mousedown: # Vẽ hoặc cập nhật hình vẽ spot = pygame.mouse.get_pos() # máy lưu toạ độ của vị trí hiện tại của chuột pygame.draw.circle(screen, YELLOW, spot, radius) # vẽ vòng tròn trên screen, màu vàng ở tại toạ độ trong spot và bán kính radius pygame.display.update() # cập nhật màn hình pygame.quit() # Exit
Kết quả: Run->Run Module chương trình sẽ cho ta vẽ 1 hình như ta đang cầm cọ để vẽ: nếu bấm chuột và kéo lê (click & drag) thì vẽ được một đường liên tục, nhưng nếu buông (release) chuột thì đường vẽ bị gían đoạn. Hình 9-2 là một ví dụ. Bạn cho trẻ vẽ quệt cái gì thì quệt. Just for FUN!
2) CHO NỔ (EXPLOSION) ĐỐI TƯỢNG (OBJECT)
Ta lập một chương trình cho xuất hiện khắp nơi trên màn hình “thằng quỷ” hay con ma (Sprite) có cái mặt cười, rồi bấm nút hạ chúng (cho nổ).
SPRITE: là ý niệm có từ những ngày đầu của trò chơi video (video games). Những đối tượng bằng hình vẽ được gọi là sprites (ma quỉ) trên màn hình vì chúng di chuyển, trôi nổi trên nền màn hình, giống như những con ma trong những chuyện thần tiên tưởng tượng.
CLASS (Hạng) và OBJECTS (đối tượng)
CLASS: Trong Pygame là những bản mẫu (template) mà ta có thể sử dụng để dùng lại những đối tượng (objects: những hình vẽ, ảnh…) và mỗi đối tượng này lại có đầy đủ những hàm (functions) cùng những gì riêng biệt (properties) của chúng). Trong chương trình sau này của chúng ta, các bạn sẽ thấy đối tượng là “con ma” (sprite) có cái mặt cười có những điểm riêng biệt cần được chú ý là: vị trí của chúng trên màn hình, kích thước cuả chúng, và tốc độ của chúng khi di chuyển theo hướng x và y. Vì vậy ta lập cho chúng một CLASS riêng với những thuộc tính (attributes) nói trên. Khi cần bạn có thể tự mình lập những class riêng để dùng làm mẫu.
Những class và đối tượng kiểu “ma” (sprites) như thế này có thể được hợp lại thành nhóm, gọi chung là Group Class (nhóm hạng).
a) Ta sẽ lập CLASS cho “con ma” Smiley của chúng ta như sau:
class Smiley(pygame.sprite.Sprite) # Smiley là tên của class do ta đặt pos = (0,0) # vị trí toạ độ đầu tiên của “con ma” xvel = 1 # vận tốc trên hoành độ, theo chiều ngang màn hình, cho là 1 pixel yvel = 1 # vận tốc trên tung độ, theo chiều dọc màn hình, cho là 1 pixel. Vel: velocity scale = 100 # mỗi “con ma” to bao nhiêu pixels (size), ở đây cho là 100
Chú thích: pygame.sprite.Sprite được gọi là phần nới rộng (extending) thêm của Pygame. Khi bạn tạo một class cho Sprite bạn cần gọi pygame này:
pygame.sprite.Sprite, trong đó chứa tất cả những gì cần thiết cho đồ hoạ sprite (sprite graphics).
b) KHỞI ĐỘNG
Sau khi tạo Class cho Smiley, ta cần KHỞI ĐỘNG (INITIALIZE) nó.
Nhờ hàm _init_ của Pygame ta mới có thể khởi động CLASS được. Khởi động ở đây có nghiã ta cho những trị ban đầu (starting values) cho đối tượng sprite của chúng ta. Ta phải cung cấp cho hàm _init_ những thông số (parameters) nó cần. Trong ví dụ này ta phải cung cấp cho nó vị trí mà mình muốn con Smiley xuất hiện (trong pos), cũng như trị của vận tốc khởi đầu trên trục x (xvel) và trục y (yvel), vị trí của hình chữ nhật bao quanh nó (rect.x và rect.y). Vì con Smiley của ta là một class và tất cả những con sprites có mặt cười sẽ là những đối tượng của loại Smiley. Thông số đầu tiên của tất cả những hàm trong class sẽ chính là con ma cười. Ta gọi thông số đó là self vì nó liên kết hàm _init_ và những hàm khác với dữ liệu riêng của đối tượng. Sau đây là ta tự tạo một hàm để khởi động cái class có tên Smiley:
def _init_(self, pos, xvel, yvel): pygame.sprite.Sprite._init_(self) self.image = pic # ảnh từ diã lưu vào ảnh của đối tượng sprite self.rect = self.image.get_rect() # cho kích thước của hình chữ nhật bao quanh ảnh 100x100 self.pos = pos # self.rect.x = pos[0] - self.scale/2 self.rect.y = pos[1] - self.scale/2 self.xvel = xvel self.yvel = yvel
Bạn xem hình 9-3 để hiểu rõ hàm def_init_ trên hơn:
c) CẬP NHẬT SPRITES (update sprites)
Cập nhật những sprites có mặt cười (smiley sprites) cho một khung hình (frame) là thay đổi vị trí của mỗi sprite tuỳ theo vận tốc của nó và kiểm soát xem nó có đụng vào thành của màn hình chưa. Sau đây là hàm để cập nhật smiley sprite:
update(self): self.rect.x += self.xvel self.rect.y += selv.yvel if self.rect.x <= 0 or self.rect.x > screen.get¬_width() – self.scale: self.xvel = -self.xvel if self.rect.y <= 0 or self.rect.y > screen.get_height() – self.scale: self.yvel = -self.yvel
d) THAY ĐỔI SCALE (SIZE) CỦA HÌNH ẢNH
Đầu tiên phải thay đổi kích thước của biến scale của đối tượng bằng một số ngẫu nhiên trong khoảng 10 và 100. Như thế gọi là transformation (biến dạng) bằng cách dùng hàm pygame.transform.scale() như sau:
Self.scale = random.randrange(10,100) Self.image = pygame.transform.scale(self.image, (self.scale,self.scale))
Hàm transform.scale() của Pygame sẽ lấy một hình ảnh ( self.image) và các kích thước mới (là trị ngẫu nhiên của self.scale) làm chiều rộng và chiều cao của hình được biến dạng và nó trả trở lại hình ảnh đã được thay đổi kích thước (lên hay xuống, to hơn hay bé hơn), hình ảnh đang lưu trữ như là self.image mới.
Sau đây là chương trình đầy đủ:
# SmileyExplosion.py import pygame import random BLACK = (0,0,0) pygame.init() screen = pygame.display.set_mode([800,600]) pygame.display.set_caption("Pop a Smiley") mousedown = False keep_going = True clock = pygame.time.Clock() pic = pygame.image.load("CrazySmile.bmp") colorkey = pic.get_at((0,0)) # pic.set_colorkey(colorkey) sprite_list = pygame.sprite.Group() class Smiley(pygame.sprite.Sprite): pos = (0,0) xvel = 1 yvel = 1 scale = 100 def __init__(self, pos, xvel, yvel): pygame.sprite.Sprite.__init__(self) self.image = pic self.scale = random.randrange(10,100) self.image = pygame.transform.scale(self.image, (self.scale,self.scale)) self.rect = self.image.get_rect() self.pos = pos self.rect.x = pos[0] - self.scale/2 self.rect.y = pos[1] - self.scale/2 self.xvel = xvel self.yvel = yvel def update(self): self.rect.x += self.xvel self.rect.y += self.yvel if self.rect.x <= 0 or self.rect.x > screen.get_width() - self.scale: self.xvel = -self.xvel if self.rect.y <= 0 or self.rect.y > screen.get_height() - self.scale: self.yvel = -self.yvel while keep_going: for event in pygame.event.get(): if event.type == pygame.QUIT: keep_going = False if event.type == pygame.MOUSEBUTTONDOWN: if pygame.mouse.get_pressed()[0]: # Regular left mouse button, draw mousedown = True elif pygame.mouse.get_pressed()[2]: # Right mouse button, pop pos = pygame.mouse.get_pos() clicked_smileys = [s for s in sprite_list if s.rect.collidepoint(pos)] sprite_list.remove(clicked_smileys) if event.type == pygame.MOUSEBUTTONUP: mousedown = False screen.fill(BLACK) sprite_list.update() # 1 sprite_list.draw(screen) # 2 clock.tick(60) pygame.display.update() if mousedown: speedx = random.randint(-5, 5) speedy = random.randint(-5, 5) newSmiley = Smiley(pygame.mouse.get_pos(),speedx,speedy) # 3 sprite_list.add(newSmiley) # 4 pygame.quit()
Chú thích:
# 1: gọi hàm update() trên danh sách những smiley sprites lưu trữ trong sprite_list; dòng lệnh này sẽ gọi hàm cập nhật để di chuyển mỗi cái mặt cười trên màn hình và kiểm soát coi có chạm các khung màn hình chưa.
# 2: sẽ vẽ mỗi mặt cười trên màn hình vào vị trí thích hợp. Chỉ cần hai dòng mã (codes) là có thể làm chuyển động và vẽ hàng trăm sprites.
# 3: tạo một mặt cười mới có tên newSmiley.
# 4: lấy cái mặt cười mới newSmiley và thêm nó vào Group (nhóm) các sprites có tên sprite_list.
e) BẮN NỔ (pop) SMILEY
Ta sẽ kéo lê nút trái chuột để tạo các mặt cười rồi bấm nút phải chuột để bắn bể chúng như bong bóng.
“May mắn” hơn chương trình trước vì chương trình sau đây mình có hàm có sẵn của Pygame pygame.sprite.collide_rect để kiểm soát xem những hình chữ nhật chứa 2 sprites có va vào nhau không; ta cũng có hàm collide_circle() để kiểm soát xem 2 sprites tròn có chạm nhau không. Và ta cũng có thể kiểm xem một sprite có chạm vào một điểm không (chẳng hạn một pixel mà người sử dụng vừa mới bấm chuột); mình cũng có thể dùng hàm rect.collidepoint() để xét xem sprite có chồng lắp (overlap) hay đụng với điểm ấy trên màn hình không.
Xin xem chương trình sau đây để hiểu rõ hơn về những hàm mới của Pygame cùng những chú thích để tìm hiểu cách bắn bể (pop) một object ra sao.
# SmileyPop.py import pygame import random BLACK = (0,0,0) pygame.init() screen = pygame.display.set_mode([800,600]) pygame.display.set_caption("Pop a Smiley") mousedown = False keep_going = True clock = pygame.time.Clock() pic = pygame.image.load("CrazySmile.bmp") colorkey = pic.get_at((0,0)) pic.set_colorkey(colorkey) sprite_list = pygame.sprite.Group() class Smiley(pygame.sprite.Sprite): pos = (0,0) xvel = 1 yvel = 1 scale = 100 def __init__(self, pos, xvel, yvel): pygame.sprite.Sprite.__init__(self) self.image = pic self.scale = random.randrange(10,100) self.image = pygame.transform.scale(self.image, (self.scale,self.scale)) self.rect = self.image.get_rect() self.pos = pos self.rect.x = pos[0] - self.scale/2 self.rect.y = pos[1] - self.scale/2 self.xvel = xvel self.yvel = yvel def update(self): self.rect.x += self.xvel self.rect.y += self.yvel if self.rect.x <= 0 or self.rect.x > screen.get_width() - self.scale: self.xvel = -self.xvel if self.rect.y <= 0 or self.rect.y > screen.get_height() - self.scale: self.yvel = -self.yvel while keep_going: for event in pygame.event.get(): if event.type == pygame.QUIT: keep_going = False if event.type == pygame.MOUSEBUTTONDOWN: if pygame.mouse.get_pressed()[0]: # 1 : Regular left mouse button, draw mousedown = True elif pygame.mouse.get_pressed()[2]: # 2: Right mouse button, pop pos = pygame.mouse.get_pos() # 3 clicked_smileys = [s for s in sprite_list if s.rect.collidepoint(pos)] # 4 sprite_list.remove(clicked_smileys) # 5 if event.type == pygame.MOUSEBUTTONUP: mousedown = False screen.fill(BLACK) sprite_list.update() sprite_list.draw(screen) clock.tick(60) pygame.display.update() if mousedown: speedx = random.randint(-5, 5) speedy = random.randint(-5, 5) newSmiley = Smiley(pygame.mouse.get_pos(),speedx,speedy) sprite_list.add(newSmiley) pygame.quit()
Chú thích:
# 1:check có phải nút trái chuột được bấm không
# 2: check có phải nút phải chuột được bấm không
# 3:Lấy vị trí chuột và lưu trong biến pos
# 4: Đây gọi là lập trình đi tắt (programming shortcut) để tạo ra một danh sách những sprites từ sprite_list mà chúng va chạm hay đè lên điểm mà người sử dụng đã click tại vị trí pos. Nếu một sprite s trong nhóm sprite_list có một hình chữ nhật mà va chạm với điểm ở vị trí pos, phải nhóm nó lại thành một danh sách list [s] và lưu trữ danh sách ấy như là clicked_smileys. Khả năng tạo ra một danh sách, một tập hơp hay một array từ một cái khác dựa trên một điều kiện if (if condiyion) là một điểm mạnh của Python và nó làm cho việc lập trình của ta ngắn hơn cho ứng dụng này.
# 5: Hàm sprite_list.remove() sẽ dời bỏ bất cứ bao nhiêu số sprites ra khỏi một danh sách. Trong trường hợp này, nó sẽ dời bỏ tất cả những sprites nào từ sprite_list của ta mà va chạm với điểm mà người sử dụng bấm trên màn hình. Một khi những sprites này bị dời ra khỏi sprite_list, khi mà sprite_list được vẽ trong vòng lặp , thì những sprites bị clicked (bấm) s ẽ không còn trong danh sách nên chúng sẽ không được vẽ, giống như chúng bị biến mất, giống như chúng bị bóp bể như quả bóng hay bọt sà phòng.
Bài tập: Bạn download hình một máy bay j20 của Tàu khựa vào folder chứa các chương trình python py của bạn rồi sửa chương trình này cho bắn rơi chúng.
Sau đây là chương trình mẫu:
# NoF20.py – Tàu bay khựa đến đây tớ bắn cho tan tành import pygame import random BLACK = (0,0,0) pygame.init() screen = pygame.display.set_mode([800,600]) pygame.display.set_caption("Pop a Chinese Jet fighter") mousedown = False keep_going = True clock = pygame.time.Clock() pic = pygame.image.load("j20fighter.jpg") colorkey = pic.get_at((0,0)) # to make image corners transparent pic.set_colorkey(colorkey) # to make image corners transparent sprite_list = pygame.sprite.Group() class Smiley(pygame.sprite.Sprite): pos = (0,0) xvel = 1 yvel = 1 scale = 100 def __init__(self, pos, xvel, yvel): pygame.sprite.Sprite.__init__(self) self.image = pic self.scale = random.randrange(10,100) self.image = pygame.transform.scale(self.image, (self.scale,self.scale)) self.rect = self.image.get_rect() self.pos = pos self.rect.x = pos[0] - self.scale/2 self.rect.y = pos[1] - self.scale/2 self.xvel = xvel self.yvel = yvel def update(self): self.rect.x += self.xvel self.rect.y += self.yvel if self.rect.x <= 0 or self.rect.x > screen.get_width() - self.scale: self.xvel = -self.xvel if self.rect.y <= 0 or self.rect.y > screen.get_height() - self.scale: self.yvel = -self.yvel while keep_going: for event in pygame.event.get(): if event.type == pygame.QUIT: keep_going = False if event.type == pygame.MOUSEBUTTONDOWN: if pygame.mouse.get_pressed()[0]: # Regular left mouse button, draw mousedown = True elif pygame.mouse.get_pressed()[2]: # Right mouse button, pop pos = pygame.mouse.get_pos() clicked_smileys = [s for s in sprite_list if s.rect.collidepoint(pos)] sprite_list.remove(clicked_smileys) if event.type == pygame.MOUSEBUTTONUP: mousedown = False screen.fill(BLACK) sprite_list.update() sprite_list.draw(screen) clock.tick(60) pygame.display.update() if mousedown: speedx = random.randint(-5, 5) speedy = random.randint(-5, 5) newSmiley = Smiley(pygame.mouse.get_pos(),speedx,speedy) sprite_list.add(newSmiley) pygame.quit()