ภาษาโปรแกรมโดย milo อ่าน 8 นาทีเรียบเรียงโดยมี AI ช่วย

จาก Sprite Sheet สู่เกมแรกของคุณ: สอน Pygame สร้างเกม Python แบบจบในตัว

เคยตัด Sprite Sheet ไว้จนครบแล้ว แต่ยังไม่รู้จะเอามาประกอบเป็นเกมยังไง? บทความนี้จะพาคุณเขียนเกม Python จบในตัว ตั้งแต่ติดตั้ง Pygame จนถึงการชนและควบคุมตัวละคร

สารบัญ

จาก Sprite ที่ตัดไว้ สู่เกมที่เล่นได้จริง

เคยสงสัยไหมว่าเกมอย่าง Hollow Knight หรือ Stardew Valley เริ่มต้นจากอะไร? คำตอบไม่ใช่กราฟิกสวยหรู แต่คือกล่องสี่เหลี่ยมเล็กๆ ที่ขยับได้ตามคำสั่งคีย์บอร์ด

ถ้าคุณเคยอ่านบทความก่อนหน้าเรื่องการตัด Sprite Sheet คุณคงมีไฟล์ภาพตัวละครที่พร้อมใช้งานแล้ว ปัญหาคือเราจะเอาภาพพวกนั้นมาทำอะไรต่อ? บทความนี้เราจะมาต่อยอดกัน ผมจะสอน Pygame สร้างเกม Python แบบจบในตัว ตั้งแต่ติดตั้งไลบรารี โหลด Sprite ควบคุมตัวละคร ไปจนถึงตรวจสอบการชน

เป้าหมายของเราคือเกมตัวอย่างชื่อ "Coin Dash" — เกมที่ผู้เล่นควบคุมตัวละครเก็บเหรียญในจอ หลบสิ่งกีดขวาง และนับคะแนนได้

ขั้นตอนที่ 1: ติดตั้ง Pygame และเตรียมโปรเจกต์

Pygame เป็นไลบรารีที่ต่อยอดจาก SDL ช่วยให้เราเขียนเกม 2D ด้วย Python ได้โดยไม่ต้องกังวลเรื่องระบบกราฟิกระดับล่าง

เปิดเทอร์มินัลแล้วรันคำสั่ง:

pip install pygame

แนะนำให้สร้างโฟลเดอร์โปรเจกต์แยก และใช้ virtual environment เพื่อไม่ให้เกิดความสับสนกับแพ็กเกจอื่น:

mkdir coin_dash
cd coin_dash
python -m venv venv
source venv/bin/activate  # บน Windows ใช้ venv\Scripts\activate
pip install pygame

โครงสร้างไฟล์ที่ผมใช้ในบทความนี้:

coin_dash/
├── main.py
├── assets/
│   ├── player_sheet.png
│   └── coin.png

ไฟล์ player_sheet.png คือ Sprite Sheet ที่เราตัดไว้ ส่วน coin.png เป็นไอเทมที่ผู้เล่นจะเก็บ

ขั้นตอนที่ 2: โหลด Sprite จาก Sheet อย่างถูกวิธี

ปัญหาที่มือใหม่มักเจอคือโหลดทั้งภาพ Sheet มาแสดง ทำให้ตัวละครมีเพื่อนแสดงข้างๆ ตลอดเวลา วิธีที่ถูกคือใช้เมธอด subsurface หรือ Surface.subsurface() เพื่อตัดเฟรมที่ต้องการออกมา

สมมติ Sheet ของเรามี 4 เฟรมเรียงแนวนิ่ง แต่ละเฟรมกว้าง 32px สูง 32px:

import pygame

def load_sprite_sheet(filename, frame_width, frame_height, columns, rows):
    sheet = pygame.image.load(filename).convert_alpha()
    frames = []
    for row in range(rows):
        for col in range(columns):
            x = col * frame_width
            y = row * frame_height
            rect = pygame.Rect(x, y, frame_width, frame_height)
            frame = sheet.subsurface(rect)
            frames.append(frame)
    return frames

ฟังก์ชันนี้รับชื่อไฟล์ ขนาดเฟรม จำนวนคอลัมน์และแถว แล้วคืนลิสต์ของ Surface ที่ตัดแล้ว คุณสามารถวนลูปเล่นเฟรมเพื่อสร้างอนิเมชันได้

เคล็ดลับสำคัญ: เรียก .convert_alpha() หลังโหลดภาพเสมอ ถ้าไม่ทำ Pygame จะช้าลงมากเพราะต้องแปลงฟอร์แมตพิกเซลทุกครั้งที่วาด

ขั้นตอนที่ 3: สร้างคลาส Player และควบคุมด้วยคีย์บอร์ด

เกมที่ดีไม่ควรมีโค้ดกระจัดกระจาย ผมแนะนำให้แยกตัวละครเป็นคลาส สืบทอดจาก pygame.sprite.Sprite ซึ่งเป็นคลาสพื้นฐานที่ Pygame มอบให้

class Player(pygame.sprite.Sprite):
    def __init__(self, x, y, frames):
        super().__init__()
        self.frames = frames
        self.current_frame = 0
        self.animation_timer = 0
        self.image = self.frames[self.current_frame]
        self.rect = self.image.get_rect(topleft=(x, y))
        self.speed = 5
        self.velocity_y = 0
        self.gravity = 0.5

    def update(self, keys):
        # ควบคุมด้วยคีย์บอร์ด
        if keys[pygame.K_LEFT]:
            self.rect.x -= self.speed
        if keys[pygame.K_RIGHT]:
            self.rect.x += self.speed
        if keys[pygame.K_UP]:
            self.rect.y -= self.speed
        if keys[pygame.K_DOWN]:
            self.rect.y += self.speed

        # อนิเมชัน
        self.animation_timer += 1
        if self.animation_timer >= 10:
            self.animation_timer = 0
            self.current_frame = (self.current_frame + 1) % len(self.frames)
            self.image = self.frames[self.current_frame]

สังเกตว่าผมใช้ keys = pygame.key.get_pressed() ในลูปหลัก แล้วส่ง keys เข้าไปใน update วิธีนี้ทำให้ผู้เล่นกดปุ่มค้างได้ ต่างจาก pygame.KEYDOWN ที่ทำงานทีเดียวต่อครั้ง

ทำไมไม่ใช้ KEYDOWN?

เพราะเกมแอ็กชันต้องการการตอบสนองต่อเนื่อง ถ้าใช้ KEYDOWN ผู้เล่นจะต้องกดซ้ำเพื่อเดินทีละก้าว ซึ่งไม่ใช่ประสบการณ์ที่ดี

ขั้นตอนที่ 4: ตรวจสอบการชนด้วย Collision Detection

การชน (Collision) คือหัวใจของเกม ไม่ว่าจะเก็บไอเทม ชนศัตรู หรือชนกำแพง ล้วนใช้การตรวจสอบนี้

Pygame มีวิธีตรวจสอบการชนหลายแบบ แต่ที่ใช้บ่อยคือ spritecollide และ colliderect

4.1 การชนระหว่าง Sprite กับ Group

สมมติเรามีกลุ่มเหรียญที่ผู้เล่นต้องเก็บ:

coins = pygame.sprite.Group()
for i in range(5):
    coin = Coin(100 + i * 60, 200)
    coins.add(coin)

# ในลูปหลัก
hit_coins = pygame.sprite.spritecollide(player, coins, True)
for coin in hit_coins:
    score += 10

พารามิเตอร์ตัวที่สาม True หมายถึงลบ Sprite ที่ชนออกจาก Group อัตโนมัติ ถ้าไม่ต้องการให้ลบ ให้ใส่ False

4.2 การชนกับกำแพงแบบกั้นการเคลื่อนที่

กรณีที่ตัวละครไม่ควรทะลุกำแพง ต้องเช็คก่อนย้ายตำแหน่ง:

def update(self, keys, walls):
    old_x, old_y = self.rect.x, self.rect.y

    if keys[pygame.K_LEFT]:
        self.rect.x -= self.speed
    if keys[pygame.K_RIGHT]:
        self.rect.x += self.speed

    # ตรวจสอบการชนแนวนอน
    for wall in walls:
        if self.rect.colliderect(wall.rect):
            self.rect.x = old_x
            break

    if keys[pygame.K_UP]:
        self.rect.y -= self.speed
    if keys[pygame.K_DOWN]:
        self.rect.y += self.speed

    # ตรวจสอบการชนแนวตั้ง
    for wall in walls:
        if self.rect.colliderect(wall.rect):
            self.rect.y = old_y
            break

ที่ต้องแยกแนวนอนกับแนวตั้งเพราะถ้ารวมกัน ตัวละครจะติดกำแพงและเลื่อนไม่ได้เลย การแยกช่วยให้ผู้เล่นเลื่อนไปตามผนังได้

ประกอบทุกอย่างเป็นเกมจบในตัว

ตอนนี้เรามีชิ้นส่วนครบแล้ว มาประกอบเป็นเกมที่เล่นได้จริง:

import pygame
import sys

# กำหนดค่าคงที่
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FPS = 60

class Coin(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((20, 20))
        self.image.fill((255, 215, 0))
        self.rect = self.image.get_rect(center=(x, y))

class Wall(pygame.sprite.Sprite):
    def __init__(self, x, y, w, h):
        super().__init__()
        self.image = pygame.Surface((w, h))
        self.image.fill((100, 100, 100))
        self.rect = self.image.get_rect(topleft=(x, y))

def main():
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    pygame.display.set_caption("Coin Dash")
    clock = pygame.time.Clock()
    font = pygame.font.Font(None, 36)

    # โหลด Sprite Sheet (สมมติมี 4 เฟรม ขนาด 32x32)
    player_frames = load_sprite_sheet("assets/player_sheet.png", 32, 32, 4, 1)
    player = Player(50, 50, player_frames)

    coins = pygame.sprite.Group()
    for i in range(8):
        coins.add(Coin(200 + i * 70, 300))

    walls = pygame.sprite.Group()
    walls.add(Wall(150, 150, 200, 20))
    walls.add(Wall(400, 400, 20, 150))

    all_sprites = pygame.sprite.Group(player, coins, walls)

    score = 0
    running = True

    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        keys = pygame.key.get_pressed()
        player.update(keys, walls)

        # ตรวจสอบการชนกับเหรียญ
        hit_coins = pygame.sprite.spritecollide(player, coins, True)
        score += len(hit_coins) * 10

        # วาด
        screen.fill((30, 30, 40))
        all_sprites.draw(screen)

        score_text = font.render(f"Score: {score}", True, (255, 255, 255))
        screen.blit(score_text, (10, 10))

        pygame.display.flip()
        clock.tick(FPS)

    pygame.quit()
    sys.exit()

if __name__ == "__main__":
    main()

รันด้วย python main.py แล้วคุณจะได้เกมที่ควบคุมตัวละครเดินได้ ชนกำแพงไม่ทะลุ และเก็บเหรียญเพิ่มคะแนน

จุดที่มือใหม่มักพลาด

ลืมเรียก update ของ Group

ถ้าใช้ all_sprites.update() โดยไม่ส่งพารามิเตอร์ คลาส Player จะ error เพราะต้องการ keys และ walls วิธีแก้คือเรียก player.update() แยก หรือ override ให้คลาสอื่นรับพารามิเตอร์เดียวกันได้

ลืม convert_alpha

ภาพที่ไม่ convert จะมีปัญหา transparency และทำให้เกมกระตุก โดยเฉพาะเมื่อมี Sprite จำนวนมาก

ตรวจสอบการชนผิดจังหวะ

ถ้าตรวจสอบการชนก่อน player.update() ตำแหน่งยังไม่อัปเดต ผลลัพธ์จะผิด ต้องตรวจสอบหลัง update เสมอ

ต่อยอด: อะไรต่อจากนี้?

เกมนี้ยังขาดหลายอย่าง เช่น เสียง หน้าเมนู ระบบเกิดใหม่ หรือศัตรูที่เคลื่อนไหว ลองทำทีละอย่าง:

  • เพิ่มเสียงเก็บเหรียญด้วย pygame.mixer.Sound
  • สร้างหน้าเมนูเริ่มเกม
  • เพิ่มศัตรูที่เดินแบบสุ่มและใช้ collision ตรวจสอบการชนกับผู้เล่น
  • เพิ่มระบบเกิดใหม่เมื่อชนศัตรู

สรุป

จาก Sprite Sheet ที่ตัดไว้ ตอนนี้คุณมีเกมที่เล่นได้จริงแล้ว เราผ่านการติดตั้ง Pygame โหลด Sprite แยกเฟรม ควบคุมตัวละครด้วยคีย์บอร์ด และตรวจสอบการชนทั้งแบบเก็บไอเทมและแบบกั้นการเคลื่อนที่

สิ่งที่สำคัญที่สุดไม่ใช่โค้ด แต่คือการเข้าใจว่าแต่ละส่วนทำงานยังไง เมื่อเข้าใจแล้ว คุณจะปรับใช้กับเกมแนวอื่นได้

ลองรันโค้ดนี้แล้วดูว่าเกมของคุณเล่นได้ยังไง ถ้าเจอปัญหาหรืออยากต่อยอดเพิ่ม ฝากคอมเมนต์ไว้คุยกันได้เลย — คุณอยากเพิ่มฟีเจอร์อะไรเป็นอย่างแรกหลังจากนี้?

เนื้อหาที่จัดทำโดยมี AI ช่วยจะมีป้ายกำกับ "เรียบเรียงโดยมี AI ช่วย" เพื่อให้คุณทราบอย่างชัดเจน เราถือว่าความโปร่งใสเรื่องการใช้ AI เป็นสิ่งสำคัญต่อความไว้วางใจของผู้อ่าน

พบข้อมูลที่ไม่ถูกต้องหรือคลาดเคลื่อน?เข้าสู่ระบบเพื่อทักท้วง
บทความนี้เป็นอย่างไร?

ยังไม่มีความคิดเห็น — มาเป็นคนแรกกันเถอะ!