Xây dựng trò chơi trượt tuyết bằng thư viện pygame

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:

  1. 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
  2. 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
  3. 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

Thẻ: pygame python game-development sprite collision-detection

Đăng vào ngày 6 tháng 8 lúc 01:29