Xây dựng trò chơi trượt tuyết bằng thư viện pygame
Mô tả thực nghiệm
Các thông tin sau đây là mô tả môi trường mặc định của phòng thí nghiệm, nếu bạn đang sử dụng môi trường tùy chỉnh, vui lòng điều chỉnh cho phù hợp với môi trường của bạn.
1. Đăng nhập hệ thống
Không cần mật khẩu để đăng nhập tự động, tên người dùng hệ thống là shiyanlou, tài khoản này có quyền sudo và có thể thực hiện các thao tác quản trị như cài đặt phần mềm.
2. Giới thiệu môi trường
Môi trường thực nghiệm sử dụng hệ điều hành Ubuntu Linux có giao diện đồ họa, trong quá trình thực nghiệm sẽ sử dụng các chương trình trên màn hình:
- LXTerminal: Màn hình lệnh Linux, mở ra sẽ vào môi trường Bash, có thể sử dụng các lệnh Linux
- Firefox: Trình duyệt web, có thể sử dụng trong các khóa học cần giao diện phía trước, chỉ cần mở file HTML/JS trong môi trường
- GVim: Trình soạn thảo rất hữu ích, cách sử dụng đơn giản nhất có thể tham khảo khóa học Vim editor
3. Sử dụng môi trường
Sử dụng trình soạn thảo để nhập mã và file cần thiết cho thực nghiệm, sử dụng terminal để chạy các lệnh cần thiết.
Trang "Ghi chép thực nghiệm" có thể xem tại "Trang chủ cá nhân", bao gồm ảnh chụp màn hình, ghi chú và thời gian học tập hiệu quả (thời gian thao tác trong môi trường thực nghiệm, nếu không thao tác hệ thống sẽ ghi nhận là thời gian ngắt quãng). Đây là bằng chứng xác thực quá trình học tập trên nền tảng.
Tất cả mã nguồn trong khóa học này có thể tải về bằng lệnh trong terminal Xfce:
wget http://labfile.oss.aliyuncs.com/courses/826/skierGames.py
Giới thiệu khóa học
1. Các điểm kiến thức:
Trong thực nghiệm này sẽ giới thiệu cách phát triển nhanh trò chơi nhỏ bằng Python và pygame trong môi trường desktop Linux.
2. Tài liệu tham khảo:
http://www.pygame.org/docs/
3. Cài đặt gói phụ thuộc
$ sudo apt-get update
$ sudo apt-get install python-pygame
4. File phụ thuộc
Vì trò chơi của chúng ta chứa một số hình ảnh và âm thanh nền nên cần tải nhạc nền và hình ảnh:
wget http://labfile.oss.aliyuncs.com/courses/826/bg_music.tar.gz
wget http://labfile.oss.aliyuncs.com/courses/826/bg_img.tar.gz
Sau đó giải nén chúng:
tar -xzf bg_music.tar.gz
tar -xzf bg_img.tar.gz
Cách chạy: Đặt file mã nguồn và file hình ảnh âm thanh cùng nhau rồi chạy trong terminal:
python skierGames.py
5. Giới thiệu trò chơi:
Nhân vật chính là người trượt tuyết, khi trượt có thể va chạm với cây, khi va chạm với cây sẽ bị trừ điểm, bản đồ cũng có cờ nhỏ, khi va chạm với cờ sẽ được cộng điểm - một trò chơi truyền thống.
6. Lưu ý:
Vì phòng thí nghiệm không hỗ trợ phát âm thanh nên phần âm thanh trong mã nguồn đã bị comment lại, khi chạy trên máy tính cá nhân có thể bỏ comment để phát âm thanh.
Thiết kế kỹ thuật
Điều quan trọng nói ba lần:
Khi học module Pygame, nếu có điều gì chưa hiểu cần tham khảo tài liệu Pygame:
http://www.pygame.org/docs/
×3
1. Thiết kế nhân vật
class SkiCharacter(pygame.sprite.Sprite): # Tạo nhân vật trượt tuyết
def __init__(self):
pygame.sprite.Sprite.__init__(self) # phương thức init của lớp cha
self.image = pygame.image.load("./bg_img/skier_down.png") # hình ảnh nhân vật trượt tuyết
self.rect = self.image.get_rect() # lấy kích thước hình chữ nhật của ảnh
self.rect.center = [320, 100] # chỉ định vị trí trung tâm của hình chữ nhật
self.direction = 0
def rotate(self, movement): # hàm rẽ của nhân vật trượt tuyết, tham số movement chỉ định hướng và mức độ di chuyển
self.direction = self.direction + movement #tốc độ di chuyển hiện tại của nhân vật
if self.direction < -2: self.direction = -2 # giới hạn 5 chế độ di chuyển cho nhân vật
if self.direction > 2: self.direction = 2
center_pos = self.rect.center
self.image = pygame.image.load(ski_images[self.direction]) #hình ảnh tư thế tương ứng lúc này
self.rect = self.image.get_rect()
self.rect.center = center_pos
velocity = [self.direction, 6 - abs(self.direction) * 2] #tốc độ trượt tuyết
return velocity
def shift(self, velocity): #di chuyển trái phải của nhân vật trượt tuyết
self.rect.centerx = self.rect.centerx + velocity[0] #vị trí hiện tại của nhân vật
if self.rect.centerx < 20: self.rect.centerx = 20 # giới hạn vị trí tối đa và tối thiểu của nhân vật
if self.rect.centerx > 620: self.rect.centerx = 620
Lớp này tạo ra hình ảnh và hành vi của nhân vật trượt tuyết (trượt trái phải và tư thế khi ngã), tính toán tốc độ di chuyển khác nhau và giới hạn di chuyển trái phải (không nên vượt quá phạm vi giao diện).
2. Tạo cây và cờ
class BarrierClass(pygame.sprite.Sprite): #tạo cây và cờ
def __init__(self, image_path, position, category):
pygame.sprite.Sprite.__init__(self)
self.image_path = image_path #image_path có thể là cây hoặc cờ
self.image = pygame.image.load(image_path) #nạp hình ảnh hiện tại
self.position = position
self.rect = self.image.get_rect()
self.rect.center = position
self.category = category
self.crossed = False
def slide(self, terrain_offset): #cuộn cảnh lên tạo hiệu ứng trượt xuống
self.rect.centery = self.position[1] - terrain_offset
Lớp này tạo ra cây hoặc cờ và tạo hiệu ứng chuyển động cảnh.
3. Vị trí ngẫu nhiên cho cây và cờ
def generate_level(start_row, end_row): # tạo cửa sổ chứa cây và cờ ngẫu nhiên
barriers = pygame.sprite.Group() # tạo nhóm đối tượng độc lập
positions = []
flags = pygame.sprite.Group()
for i in range(10):
row_num = random.randint(start_row, end_row) #lấy số ngẫu nhiên trong khoảng start_row đến end_row
col_num = random.randint(0, 9)
pos = [col_num * 64 + 20, row_num * 64 + 20] # xác định vị trí
if not (pos in positions): # nếu vật thể được định nghĩa ở trên không nằm trong danh sách vật thể đã xác định thuộc tính
positions.append(pos)
category = random.choice(["tree", "flag"])#chọn ngẫu nhiên giữa cây hoặc cờ
if category == "tree": img_path = "./bg_img/skier_tree.png" #chọn hình ảnh tương ứng
elif category == "flag": img_path = "./bg_img/skier_flag.png"
barrier = BarrierClass(img_path, pos, category) #thêm vật thể đã tạo vào trò chơi
barriers.add(barrier)
return barriers
Dùng để tạo cửa sổ và sinh ngẫu nhiên cây và cờ ở vị trí ngẫu nhiên, đồng thời đặt hình ảnh tương ứng vào vị trí ngẫu nhiên.
4. Vẽ nền màn hình và cập nhật hình ảnh
Mã rất đơn giản, xem bên dưới:
def refresh_display(): # vẽ lại màn hình khi có sự di chuyển
screen.fill([255, 255, 255]) #chỉ định màu nền (RGB)
pygame.display.update(barriers.draw(screen))#hàm này là phiên bản tối ưu của pygame.display.flip. Nó chỉ cho phép cập nhật một phần màn hình chứ không phải toàn bộ khu vực. Nếu không có tham số truyền vào nó cập nhật toàn bộ diện tích bề mặt như pygame.display.flip().
screen.blit(ski_character.image, ski_character.rect)# dùng để vẽ bitmap, vẽ ski_character.image tại vị trí ski_character.rect trên màn hình
screen.blit(score_display, [10, 10])
pygame.display.flip()
Logic chương trình:
Đầu tiên chúng ta khởi tạo module Pygame và module phát âm thanh (vì phòng thí nghiệm không hỗ trợ âm thanh nên phần mã âm thanh đã bị comment lại, khi chạy trên máy cá nhân cần bỏ comment.). Sau đó khởi tạo cửa sổ trò chơi, nạp module lắng nghe sự kiện, tạo ngẫu nhiên cây và cờ. Sau đó bắt đầu chạy trò chơi trong vòng lặp vô hạn, thiết lập tốc độ khung hình cập nhật trò chơi, sử dụng kiểm soát bàn phím để theo dõi các phím trái phải, thiết lập quỹ đạo di chuyển của nhân vật trượt tuyết, tạo ngẫu nhiên cây và cờ sau đó kiểm tra va chạm với cây và cờ rồi thực hiện các thao tác tương ứng.
pygame.init() #khởi tạo tất cả module của pygame
#pygame.mixer.init() # khởi tạo phần trộn âm thanh để tải và phát âm thanh
#pygame.mixer.music.load("./bg_music/bg_music.mp3") # sẽ tải tên file âm thanh này và chuẩn bị phát!
#pygame.mixer.music.set_volume(0.3)#thiết lập tham số âm lượng âm thanh trong khoảng 0-1
#pygame.mixer.music.play(-1) #điều này sẽ phát luồng âm thanh đã tải. Nếu âm thanh đang phát, nó sẽ khởi động lại. Tham số điều khiển số lần phát âm thanh. Nếu là -1 thì sẽ phát lặp vô hạn.
display = pygame.display.set_mode([640,640])# dùng để khởi tạo cửa sổ, tham số là độ phân giải
timer = pygame.time.Clock()# nạp module lắng nghe thời gian, timer còn có thể thấy ở dưới phương thức timer.tick
ski_character = SkiCharacter() # khởi tạo đối tượng SkiCharacter
velocity = [0, 6]
level_position = 0
score_points = 0
level_0 = generate_level(20, 29)
level_1 = generate_level(10, 19)
current_level = 0
barriers = update_barrier_group(level_0, level_1)
font_renderer = pygame.font.Font(None, 50)
while True:
timer.tick(30) # cập nhật đồ họa 30 lần mỗi giây - đây là tốc độ khung hình.
for evt in pygame.event.get(): # kiểm tra phím bấm hoặc đóng cửa sổ
if evt.type == pygame.QUIT: sys.exit()
if evt.type == pygame.KEYDOWN:
if evt.key == pygame.K_LEFT:
velocity = ski_character.rotate(-1)
elif evt.key == pygame.K_RIGHT:
velocity = ski_character.rotate(1)
ski_character.shift(velocity) # di chuyển nhân vật trượt tuyết
level_position += velocity[1] # cuộn cảnh
if level_position >=640 and current_level == 0: # chuyển từ một cửa sổ cảnh sang cửa sổ tiếp theo
current_level = 1
level_0 = generate_level(20, 29)
barriers = update_barrier_group(level_0, level_1)
if level_position >=1280 and current_level == 1:
current_level = 0
for obj in level_0:
obj.position[1] = obj.position[1] - 1280
level_position = level_position - 1280
level_1 = generate_level(10, 19)
barriers = update_barrier_group(level_0, level_1)
for barrier in barriers:
barrier.slide(level_position)
collision = pygame.sprite.spritecollide(ski_character, barriers, False) # kiểm tra có va chạm với cây hoặc cờ không.
if collision:
if collision[0].category == "tree" and not collision[0].crossed:# nếu va chạm với cây
score_points = score_points - 100
ski_character.image = pygame.image.load("./bg_img/skier_crash.png")
refresh_display()
pygame.time.delay(1000) # tạm dừng trò chơi 1 giây.
ski_character.image = pygame.image.load("./bg_img/skier_down.png")
ski_character.direction = 0
velocity = [0, 6]
collision[0].crossed = True
elif collision[0].category == "flag" and not collision[0].crossed:
score_points += 10
barriers.remove(collision[0])
#hiển thị điểm số
score_display = font_renderer.render("Score: " +str(score_points), 1, (0, 0, 0))
refresh_display()
Tham khảo mã đầy đủ
import pygame, sys, random
ski_images = ["./bg_img/skier_down.png", "./bg_img/skier_right1.png", "./bg_img/skier_right2.png",
"./bg_img/skier_left2.png", "./bg_img/skier_left1.png"] # liệt kê các hình ảnh có thể sử dụng
class SkiCharacter(pygame.sprite.Sprite): # Tạo nhân vật trượt tuyết
def __init__(self):
pygame.sprite.Sprite.__init__(self) # phương thức init của lớp cha
self.image = pygame.image.load("./bg_img/skier_down.png") # hình ảnh nhân vật trượt tuyết
self.rect = self.image.get_rect() # lấy kích thước hình chữ nhật của ảnh
self.rect.center = [320, 100] # chỉ định vị trí trung tâm của hình chữ nhật
self.direction = 0
def rotate(self, movement): # hàm rẽ của nhân vật trượt tuyết, tham số movement chỉ định hướng và mức độ di chuyển
self.direction = self.direction + movement #tốc độ di chuyển hiện tại của nhân vật
if self.direction < -2: self.direction = -2 # giới hạn 5 chế độ di chuyển cho nhân vật
if self.direction > 2: self.direction = 2
center_pos = self.rect.center
self.image = pygame.image.load(ski_images[self.direction]) #hình ảnh tư thế tương ứng lúc này
self.rect = self.image.get_rect()
self.rect.center = center_pos
velocity = [self.direction, 6 - abs(self.direction) * 2] #tốc độ trượt tuyết
return velocity
def shift(self, velocity): #di chuyển trái phải của nhân vật trượt tuyết
self.rect.centerx = self.rect.centerx + velocity[0] #vị trí hiện tại của nhân vật
if self.rect.centerx < 20: self.rect.centerx = 20 # giới hạn vị trí tối đa và tối thiểu của nhân vật
if self.rect.centerx > 620: self.rect.centerx = 620
class BarrierClass(pygame.sprite.Sprite): #tạo cây và cờ
def __init__(self, image_path, position, category):
pygame.sprite.Sprite.__init__(self)
self.image_path = image_path #image_path có thể là cây hoặc cờ
self.image = pygame.image.load(image_path) #nạp hình ảnh hiện tại
self.position = position
self.rect = self.image.get_rect()
self.rect.center = position
self.category = category
self.crossed = False
def slide(self, terrain_offset): #cuộn cảnh lên tạo hiệu ứng trượt xuống
self.rect.centery = self.position[1] - terrain_offset
def generate_level(start_row, end_row): # tạo cửa sổ chứa cây và cờ ngẫu nhiên
barriers = pygame.sprite.Group() # tạo nhóm đối tượng độc lập
positions = []
flags = pygame.sprite.Group()
for i in range(10):
row_num = random.randint(start_row, end_row) #lấy số ngẫu nhiên trong khoảng start_row đến end_row
col_num = random.randint(0, 9)
pos = [col_num * 64 + 20, row_num * 64 + 20] # xác định vị trí
if not (pos in positions): # nếu vật thể được định nghĩa ở trên không nằm trong danh sách vật thể đã xác định thuộc tính
positions.append(pos)
category = random.choice(["tree", "flag"])#chọn ngẫu nhiên giữa cây hoặc cờ
if category == "tree": img_path = "./bg_img/skier_tree.png" #chọn hình ảnh tương ứng
elif category == "flag": img_path = "./bg_img/skier_flag.png"
barrier = BarrierClass(img_path, pos, category) #thêm vật thể đã tạo vào trò chơi
barriers.add(barrier)
return barriers
def refresh_display(): # vẽ lại màn hình khi có sự di chuyển
screen.fill([255, 255, 255]) #chỉ định màu nền (RGB)
pygame.display.update(barriers.draw(screen))#hàm này là phiên bản tối ưu của pygame.display.flip. Nó chỉ cho phép cập nhật một phần màn hình chứ không phải toàn bộ khu vực. Nếu không có tham số truyền vào nó cập nhật toàn bộ diện tích bề mặt như pygame.display.flip().
screen.blit(ski_character.image, ski_character.rect)# dùng để vẽ bitmap, vẽ ski_character.image tại vị trí ski_character.rect trên màn hình
screen.blit(score_display, [10, 10])
pygame.display.flip()
def update_barrier_group(level_a, level_b): # chuyển sang màn hình tiếp theo của cảnh
barriers = pygame.sprite.Group()
for obj in level_a: barriers.add(obj)
for obj in level_b: barriers.add(obj)
return barriers
pygame.init() #khởi tạo tất cả module của pygame
#pygame.mixer.init() # khởi tạo phần trộn âm thanh để tải và phát âm thanh
#pygame.mixer.music.load("./bg_music/bg_music.mp3") # sẽ tải tên file âm thanh này và chuẩn bị phát!
#pygame.mixer.music.set_volume(0.3)#thiết lập tham số âm lượng âm thanh trong khoảng 0-1
#pygame.mixer.music.play(-1) #điều này sẽ phát luồng âm thanh đã tải. Nếu âm thanh đang phát, nó sẽ khởi động lại. Tham số điều khiển số lần phát âm thanh. Nếu là -1 thì sẽ phát lặp vô hạn.
display = pygame.display.set_mode([640,640])# dùng để khởi tạo cửa sổ, tham số là độ phân giải
timer = pygame.time.Clock()# nạp module lắng nghe thời gian, timer còn có thể thấy ở dưới phương thức timer.tick
ski_character = SkiCharacter() # khởi tạo đối tượng SkiCharacter
velocity = [0, 6]
level_position = 0
score_points = 0
level_0 = generate_level(20, 29)
level_1 = generate_level(10, 19)
current_level = 0
barriers = update_barrier_group(level_0, level_1)
font_renderer = pygame.font.Font(None, 50)
while True:
timer.tick(30) # cập nhật đồ họa 30 lần mỗi giây - đây là tốc độ khung hình.
for evt in pygame.event.get(): # kiểm tra phím bấm hoặc đóng cửa sổ
if evt.type == pygame.QUIT: sys.exit()
if evt.type == pygame.KEYDOWN:
if evt.key == pygame.K_LEFT:
velocity = ski_character.rotate(-1)
elif evt.key == pygame.K_RIGHT:
velocity = ski_character.rotate(1)
ski_character.shift(velocity) # di chuyển nhân vật trượt tuyết
level_position += velocity[1] # cuộn cảnh
if level_position >=640 and current_level == 0: # chuyển từ một cửa sổ cảnh sang cửa sổ tiếp theo
current_level = 1
level_0 = generate_level(20, 29)
barriers = update_barrier_group(level_0, level_1)
if level_position >=1280 and current_level == 1:
current_level = 0
for obj in level_0:
obj.position[1] = obj.position[1] - 1280
level_position = level_position - 1280
level_1 = generate_level(10, 19)
barriers = update_barrier_group(level_0, level_1)
for barrier in barriers:
barrier.slide(level_position)
collision = pygame.sprite.spritecollide(ski_character, barriers, False) # kiểm tra có va chạm với cây hoặc cờ không.
if collision:
if collision[0].category == "tree" and not collision[0].crossed:
score_points = score_points - 100
ski_character.image = pygame.image.load("./bg_img/skier_crash.png")
refresh_display()
pygame.time.delay(1000)
ski_character.image = pygame.image.load("./bg_img/skier_down.png")
ski_character.direction = 0
velocity = [0, 6]
collision[0].crossed = True
elif collision[0].category == "flag" and not collision[0].crossed:
score_points += 10
barriers.remove(collision[0])
#hiển thị điểm số
score_display = font_renderer.render("Score: " +str(score_points), 1, (0, 0, 0))
refresh_display()
Mã đầy đủ có thể tải về tại đây:
wget http://labfile.oss.aliyuncs.com/courses/826/skierGames.py