9 minute read

Khi bắt đầu học lập trình, niềm yêu thích chơi game đã luôn thúc đẩy mình phải tự tay làm ra 1 game cho riêng mình, vì thế mình đã mày mò làm thử và thấy nó không khó như mình nghĩ. Vậy nên, hôm nay mình viết bài này để chia sẻ với các bạn những điều cơ bản khi làm game bằng Python mà mình đã học được.

1. Pygame

Trước hết chúng ta cần một công cụ hỗ trợ việc lập trình game trong Python, và đó chính là Pygame.

Để cài đặt Pygame, bạn chỉ cần chạy câu lệnh sau

pip install pygame

2. Chương trình Pygame cơ bản

Sau khi cài đặt pygame hãy thử tạo một chương trình pygame cơ bản với tên basicPygame.py bao gồm các nội dung sau

  • Các thư viện cần thiết

    import pygame
    import sys
    
  • Khởi tạo đối tượng game và kích cỡ màn hình hiển thị
    # Initialize pygame object
    pygame.init()
    
    # Set size of screen
    screenWidth = 500
    screenHeight = 500
    screen = pygame.display.set_mode((screenWidth, screenHeight))
    
  • Vòng lặp chính của game
    # Game loop
    while True:
        # Get event
        for event in pygame.event.get():
            # Handle exit event
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
        # Fill screen by gray
        screen.fill((150, 150, 150))
        # Draw a blue circle at the center of the screen
        pygame.draw.circle(screen, (0, 0, 255), (250, 250), 75)
        # Flip
        pygame.display.flip()
    
  • Full source code.
    # File basicPygame.py
    import pygame
    import sys
    
    # Initialize pygame object
    pygame.init()
    
    # Set size of screen
    screenWidth = 500
    screenHeight = 500
    screen = pygame.display.set_mode((screenWidth, screenHeight))
    
    # Game loop
    while True:
        # Get event
        for event in pygame.event.get():
            # Handle exit event
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
        # Fill screen by gray
        screen.fill((150, 150, 150))
        # Draw a blue circle at the center of the screen
        pygame.draw.circle(screen, (0, 0, 255), (250, 250), 75)
        # Flip
        pygame.display.flip()
    

Chạy chương trình trên và bạn sẽ thấy một cửa sổ trông giống với hình ảnh dưới đây:

3. Vòng lặp của game

Như đã biết trong chương trình vật lý phổ thông, trong 0,1s sau khi tắt ánh sáng, mắt người vẫn còn cảm nhận về hình ảnh của vật, nhờ điều này mà người ta có thể tạo ra một hình ảnh chuyển động bằng cách trình chiếu liên tục các hình ảnh rời rạc. Đây cũng là lý thuyết khi tạo ra các chuyển động trong game, phim hoạt hình, ….

Để tạo ra một hệ thống các hình ảnh liên tục trong game, ta cần một vòng lặp để liên tục hiển thị các hình ảnh đó theo thứ tự, đó là vòng lặp game.

Tuy nhiên, các hình ảnh trong game ta không thể lưu sẵn để phát lần lượt như trong phim hoạt hình bởi các hình ảnh đó còn phụ thuộc vào người chơi. Vì thế trong vòng lặp game ta sẽ cập nhật trạng thái của tất cả vật thể của game và vẽ lại chúng.

Về cơ bản, quá trình thực hiện sẽ như sau

  1. Vẽ các vật thể
  2. Cập nhật trạng thái các vật thể
  3. Quay lại bước 1

Thử thực hành với một chương trình như sau để hiều rõ hơn về vòng lặp game

  import pygame
  import random
  import sys

  # Initialize pygame object
  pygame.init()

  # Set size of screen
  screenWidth = 500
  screenHeight = 500
  screen = pygame.display.set_mode((screenWidth, screenHeight))

  colors = ['#C7980A',
            '#F4651F',
            '#82D8A7',
            '#CC3A05',
            '#575E76',
            '#156943',
            '#0BD055',
            '#ACD338']
  color = random.choice(colors)

  # coordinate x of circle
  coordinateX = 250
  # velocity of circle
  velocity = 5

  clock = pygame.time.Clock()
  # Game loop
  while True:
      clock.tick(60)
      # Get event
      for event in pygame.event.get():
          # Handle exit event
          if event.type == pygame.QUIT:
              pygame.quit()
              sys.exit()
      # Fill screen by gray
      screen.fill((150, 150, 150))

      # Update coordinate x of circle
      if coordinateX <= 75 or coordinateX >= 425:
          color = random.choice(colors)
          velocity *= -1
      coordinateX += velocity

      # Draw circle with new coordinate x and color
      pygame.draw.circle(screen, color, (coordinateX, 250), 75)
      # Flip
      pygame.display.flip()

Về cơ bản, tại mỗi vòng lặp của game ta cập nhật tọa độ và màu của hình tròn sau đó vẽ lại nó. Và đây là kết quả:

4. Sự kiện game

Tất nhiên, game không giống một chương trình giải một bài toán nào đó, khi mà ta chỉ việc chạy chương trình và xem kết quả. Game cần sự tương tác với người dùng trong suốt quá trình chạy.

Mỗi tín hiệu mà người dùng cung cấp (di chuyển con trỏ chuột, ấn một phím, …) được gọi là một sự kiện game, tại mỗi vòng lặp game ta cần xử lý tín hiệu này và phản hồi lại bằng cách vẽ lên màn hình để tạo nên sự tương tác giữa game và người chơi.

Với pygame bạn có thể dùng pygame.event.get() để lấy tất cả sự kiện của game.

Trong các chương trình ví dụ bên trên các bạn có thể thấy đoạn code sau

  for event in pygame.event.get():
      # Handle exit event
      if event.type == pygame.QUIT:
          pygame.quit()
          sys.exit()

Đây là một sự kiện cơ bản của game dùng để thoát chương trình khi nhấn QUIT.

Để hiểu rõ hơn về sự kiện game, ta sẽ sửa chương trình trong phần 3 sao cho hình tròn không còn tự chuyển động nữa mà sẽ chuyển động sang trái, phải khi người dùng ấn các phím A, D và đổi màu khi ấn SPACE.

import pygame
import random
import sys

# Initialize pygame object
pygame.init()

# Set size of screen
screenWidth = 500
screenHeight = 500
screen = pygame.display.set_mode((screenWidth, screenHeight))

colors = ['#C7980A',
        '#F4651F',
        '#82D8A7',
        '#CC3A05',
        '#575E76',
        '#156943',
        '#0BD055',
        '#ACD338']
color = random.choice(colors)
moving = False
coordinateX = 250
velocity = 5

clock = pygame.time.Clock()
# Game loop
while True:
    clock.tick(60)
    # Get event
    for event in pygame.event.get():
        # Handle exit event
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                running = False
                pygame.quit()
                sys.exit()
            # Change color event
            if event.key == pygame.K_SPACE:
                color = random.choice(colors)
            # Move event
            if event.key == pygame.K_a:
                moving = True
                velocity = -5
            if event.key == pygame.K_d:
                moving = True
                velocity = 5
        # Stop event
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_a:
                moving = False
            if event.key == pygame.K_d:
                moving = False

    # Fill screen by gray
    screen.fill((150, 150, 150))
    # Update coordinate of circle
    if moving:
        coordinateX += velocity
    if coordinateX <= 75:
        coordinateX = 75
    if coordinateX >= 425:
        coordinateX = 425
    # Draw a blue circle at the center of the screen
    pygame.draw.circle(screen, color, (coordinateX, 250), 75)
    # Flip
    pygame.display.flip()

Kết quả khi chạy chương trình

Các sự kiện ta đã xử lý tại mỗi vòng lặp trong đoạn code trên

  • Thoát chương trình
    # Handle exit event
    if event.type == pygame.QUIT:
        pygame.quit()
        sys.exit()
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_ESCAPE:
            running = False
            pygame.quit()
            sys.exit()
    

    Ở đây ngoài việc thoát bằng cách nhấp phím QUIT trên màn hình, mình cung cấp thêm điều kiện thoát khi người dùng ấn phím ESCAPE trên bàn phím.

  • Đổi màu hình tròn
    # Change color event
    if event.key == pygame.K_SPACE:
        color = random.choice(colors)
    
  • Di chuyển hình tròn
    # Move event
    if event.key == pygame.K_a:
        moving = True
        velocity = -5
    if event.key == pygame.K_d:
        moving = True
        velocity = 5
    
  • Dừng hình tròn
    # Stop event
    if event.type == pygame.KEYUP:
        if event.key == pygame.K_a:
            moving = False
        if event.key == pygame.K_d:
            moving = False
    

Ở đây có nhiều thuộc tính hơi lạ của pygame như KEYUP, KEYDOWN. Các bạn có thể tham khảo tại pygame.key để hiểu rõ về cách lấy sự kiện của pygame.

5. Thử làm một game với pygame

Lý thuyết đã xong, bây giờ chúng ta sẽ thực hành với việc tạo 1 game với pygame

5.1. Thiết kế game

Trước khi viết một chương trình hay cụ thể là làm một game, bạn nên có một bước thiết kế cơ bản những thành phần của game. Trong phần này mình sẽ hướng dẫn các bạn làm một game với các chức năng cơ bản sau:

  • Sinh ngẫu nhiên các hình tròn từ trên đỉnh màn hình di chuyển từ trên xuống
  • Người chơi điều khiển một hình chữ nhật ở dưới cùng màn hình có thể di chuyển qua trái phải
  • Ghi điểm mỗi khi hình chữ nhật chạm vào 1 hình tròn

Sau khi đã thiết kế, chúng ta có 3 việc chính phải làm

  • Tạo class Circle mô tả các hình tròn
  • Tạo class Rectangle mô tả hình chữ nhật
  • Thiết kế sự tương tác giữa các đối tượng trên

5.2. Code

Với những thiết kế trên, ta bắt đầu xây dựng từng đối tượng và kết nối chúng lại để tạo thành 1 game như ý muốn.

Vì bài viết đã dài rồi, mà đoạn code còn nhiều nên mình sẽ chỉ show source code mà không giải thích chi tiết nữa (mình tin vào khả năng đọc hiểu code của mọi người). Các bạn có thể tham khảo thêm tại trang tài liệu của pygame.

import pygame
import random
import sys


class Circle(pygame.sprite.Sprite):

    def __init__(self, x, y, radius, colors, gravity):
        pygame.sprite.Sprite.__init__(self)

        self.radius = radius
        color = random.choice(colors)
        self.gravity = gravity

        self.surface = pygame.Surface([2*radius, 2*radius], pygame.SRCALPHA)
        self.rect = self.surface.get_rect()
        self.rect.x = x
        self.rect.y = y
        pygame.draw.circle(self.surface, color, (radius,radius), radius)

    def fall(self):
        self.rect.y += self.gravity

    def draw(self, screen):
        screen.blit(self.surface, self.rect)

class Rectangle(pygame.sprite.Sprite):

    def __init__(self, x, y, w, h, color):
        pygame.sprite.Sprite.__init__(self)

        self.surface = pygame.Surface([w, h])
        self.rect = self.surface.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.surface.fill(color)
        self.velocity = 0

    def move(self):
        self.rect.x += self.velocity

    def draw(self, screen):
        screen.blit(self.surface, self.rect)

class Game:

    def __init__(self):
        # Initialize pygame object
        pygame.init()

        # Set size of screen
        screenWidth = 500
        screenHeight = 750
        self.screen = pygame.display.set_mode((screenWidth, screenHeight))
        self.circleColors = [
                            '#F94892',
                            '#FF7F3F',
                            '#FBDF07',
                            '#89CFFD',
                            '#1CD6CE',
                            '#FEDB39',
                            '#B4FF9F',
                            ]

        self.circleList = []
        self.rectangle = Rectangle(250, 730, 150, 20, '#293462')
        self.score = 0

    def update(self):
        w, h = self.screen.get_size()

        # Move rectangle
        self.rectangle.move()
        if self.rectangle.rect.x <= 0:
            self.rectangle.rect.x = 0
        if self.rectangle.rect.x >= w - self.rectangle.rect.w:
            self.rectangle.rect.x = w - self.rectangle.rect.w

        # Move circle
        for circle in self.circleList:
            circle.fall()
            outOfScreen = circle.rect.y - circle.radius >= h
            # If circle out of screen, it disappear
            if outOfScreen:
                self.circleList.remove(circle)

            isCollide = pygame.sprite.collide_rect(circle, self.rectangle)
            if isCollide:
                self.circleList.remove(circle)
                self.score += 1

        if len(self.circleList) <= 5:
            x = random.randint(50, w - 50)
            y = 0
            gravity = random.randint(2, 7)
            circle = Circle(x, y, 20, self.circleColors, gravity)
            self.circleList.append(circle)
        for circle in self.circleList:
            circle.draw(self.screen)
        self.rectangle.draw(self.screen)

        font = pygame.font.Font('freesansbold.ttf', 32)
        text = font.render(str(self.score), True, '#293462')
        textRect = text.get_rect()
        textRect.center = (w//2, 32)
        self.screen.blit(text, textRect)

        pygame.display.update()

    def run(self):
        clock = pygame.time.Clock()
        # Game loop
        while True:
            clock.tick(60)
            # Get event
            for event in pygame.event.get():
                # Handle exit event
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        pygame.quit()
                        sys.exit()
                    # Move event
                    if event.key == pygame.K_a:
                        self.rectangle.velocity = -5
                    if event.key == pygame.K_d:
                        self.rectangle.velocity = 5
                # Stop event
                if event.type == pygame.KEYUP:
                    if event.key == pygame.K_a or event.key == pygame.K_d:
                        self.rectangle.velocity = 0

            # Fill screen by gray
            self.screen.fill('#6E85B7')
            self.update()

def main():
    """ Main function."""
    game = Game()
    game.run()
# Run function main.
if __name__ == "__main__":
    main()

Và đây là demo cho game này

6. Lời kết

Những gì mình viết trên đây là những điều rất cơ bản khi làm game, còn rất nhiều điều thú vị khác mà bạn có thể tìm hiểu và mày mò trong quá trình làm game. Trong tương lai mình sẽ viết nhiều hơn về chủ đề này, mong mọi người sẽ thích.