Pygame เตรียมคลาส pygame.sprite.Sprite มาให้แล้ว ซึ่งรองรับ OOP โดยธรรมชาติ
class GameObject(pygame.sprite.Sprite): def __init__(self): # ต้องเรียก super().__init__() เสมอ! super().__init__() # 2 สิ่งที่ขาดไม่ได้สำหรับ Sprite: self.image = pygame.Surface((32, 32)) # หน้าตา self.rect = self.image.get_rect() # ตำแหน่งและขนาด def update(self): # ใส่ Logic การเคลื่อนที่ที่นี่ pass
รูปแบบมาตรฐานของการทำงานเกม แยกเป็น 3 ขั้นตอนวนซ้ำ
รับค่าจากผู้เล่น
คำนวณฟิสิกส์/สถานะ
วาดภาพขึ้นจอ
class Player:
def update(self):
# รับ Input
keys = pygame.key.get_pressed()
# คำนวณฟิสิกส์
self.rect.x += 5
# เล่นเสียง
pygame.mixer.Sound("jump.wav").play()
Player ทำทุกอย่าง: Input, Logic, Audio
class Player(Sprite):
def update(self, inputs):
# สนใจแค่ Logic การขยับ
self.rect.x += self.speed * inputs.x
class InputHandler:
# จัดการ Input แยกต่างหาก
แยก InputHandler และ SoundManager ออกมา
"เปิดรับการขยาย (เพิ่มศัตรูใหม่) แต่ปิดการแก้ไข (ไม่ต้องแก้โค้ดลูปหลัก)"
Base Class (Abstract)
class Enemy(ABC):
@abstractmethod
def attack(self): pass
Extensions
class Zombie(Enemy):
def attack(self): bite()
class Alien(Enemy):
def attack(self): shoot_laser()
for e in enemies: e.attack() "คลาสลูกต้องแทนที่คลาสแม่ได้โดยเกมไม่พัง"
คลาส Turret (ป้อมปืน) สืบทอดจาก Enemy แต่ในเมธอด move() ดันโยน Error เพราะป้อมปืนเดินไม่ได้
--> ทำให้ Game Loop ที่เรียก enemy.move() พัง
แยก Interface: MovableEnemy และ StaticEnemy หรือใช้เทคนิค Composition (มี component Movement หรือไม่)
"อย่าบังคับให้ Game Object ต้อง Implement เมธอดที่ไม่ได้ใช้"
class IGameObject: def update() def draw() def play_sound() def handle_input() def collide()
ต้นไม้ (Tree) ต้อง implement play_sound() หรอ?
class IDrawable: def draw() class IUpdatable: def update() class Tree(IDrawable): ...
ต้นไม้เลือกเป็นแค่สิ่งที่วาดได้ (Drawable)
ตัวละคร (High Level) ไม่ควรผูกติดกับ Keyboard (Low Level) โดยตรง เพราะเราอาจจะอยากใช้ JoyStick ในอนาคต
class Player: # ผิด: ผูกกับ Pygame Keyboard โดยตรง (Tight Coupling) def handle_input(self): keys = pygame.key.get_pressed() if keys[pygame.K_SPACE]: jump() # ถูก: รับ Command เข้ามา (Dependency Injection) def handle_input(self, command: ICommand): command.execute(self)
แทนที่จะสร้าง Class ลึกๆ ให้สร้าง Object จากการประกอบชิ้นส่วน (Components)
class GameObject(pygame.sprite.Sprite): def __init__(self): super().__init__() # Has-A relationships self.transform = Transform() self.renderer = SpriteRenderer() self.audio = AudioSource() self.physics = RigidBody()
ใช้สำหรับสร้าง Game Objects (Spawning) จำนวนมากและหลากหลาย
แทนที่จะ new Zombie() กระจายไปทั่วโค้ด ให้ใช้ Factory จัดการ การสุ่มประเภทและตำแหน่งเกิด
class EnemyFactory: @staticmethod def create_enemy(type, x, y): if type == "ZOMBIE": return Zombie(x, y) elif type == "ALIEN": return Alien(x, y) # Usage enemies.add(EnemyFactory.create_enemy("ZOMBIE", 100, 200))
จัดการ Events: ทำอย่างไรให้ HP Bar ลดลงเมื่อ Player โดนโจมตี โดยที่ Player ไม่ต้องรู้จัก HP Bar?
Player ทำหน้าที่แค่ notify_observers("DAMAGE") ใครที่ Subscribe อยู่ก็จะทำงานของตัวเอง (Decoupling)
จัดการสถานะของเกม (Menu, Playing, Paused, GameOver)
current_state.update() ✅ แยกไฟล์ตามหน้าที่ (Modularity)
✅ รวมค่าคงที่ไว้ที่เดียว (Config)
✅ แยก Assets ออกจาก Code
คลาสที่ทำหน้าที่ควบคุมระบบย่อยต่างๆ (มักใช้ Singleton หรือ Static)
จัดการ Group ของ Sprite ทั้งหมด (All_sprites, Enemies, Bullets)
ตรวจสอบการชนกันและสั่งการเมื่อเกิดการชน (แยก Logic นี้ออกจาก Game Loop)
โหลดและเก็บภาพ/เสียง (Cache) เพื่อไม่ให้โหลดซ้ำซ้อน (Flyweight Pattern)
Pygame มีระบบ Event Queue ของตัวเอง เราสามารถสร้าง Custom Event ได้
เทคนิคนี้ช่วยลดการผูกมัด (Decoupling) ระหว่าง Enemy และ Score System
บังคับให้ Game Object ทุกตัวต้องมีโครงสร้างตามสัญญา (Contract)
from abc import ABC, abstractmethod class Entity(pygame.sprite.Sprite, ABC): def __init__(self, groups): super().__init__(groups) @abstractmethod def movement(self): pass @abstractmethod def attack(self): pass
ใช้ Multiple Inheritance เพื่อแปะความสามารถให้ Class (Composition via Inheritance)
มี method animate() สำหรับสลับเฟรมภาพ
มี method shoot() และจัดการ Cooldown
Workshop: สร้างเกม "Space Shooter" ด้วยสถาปัตยกรรม OOP
สร้างคลาส Game เพื่อเป็น Manager หลัก (แทนที่จะเขียน loop ลอยๆ)
import pygame, sys class Game: def __init__(self): pygame.init() self.screen = pygame.display.set_mode((800, 600)) self.clock = pygame.time.Clock() self.running = True # Groups: หัวใจของการจัดการ Sprite ใน Pygame self.all_sprites = pygame.sprite.Group() def handle_input(self): for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False def update(self): self.all_sprites.update() # Polymorphism: เรียก update ของทุกตัว def draw(self): self.screen.fill((30, 30, 30)) self.all_sprites.draw(self.screen) pygame.display.flip() def run(self): while self.running: self.handle_input() self.update() self.draw() self.clock.tick(60)
สืบทอดจาก pygame.sprite.Sprite เพื่อใช้ความสามารถของ Pygame
class Player(pygame.sprite.Sprite): def __init__(self, groups): super().__init__(groups) # Add to groups auto self.image = pygame.Surface((50, 40)) self.image.fill('red') self.rect = self.image.get_rect(center=(400, 500)) self.speed = 5 def input(self): keys = pygame.key.get_pressed() if keys[pygame.K_RIGHT]: self.rect.x += self.speed if keys[pygame.K_LEFT]: self.rect.x -= self.speed def update(self): self.input() # SRP: แยกรับค่าอินพุต self.constraint() # SRP: แยกการจำกัดขอบเขต
แทนที่จะเขียนโค้ดยิงปืนใน Player ให้สร้างคลาส Weapon (Has-A relationship)
class Laser(pygame.sprite.Sprite): def __init__(self, pos, groups): super().__init__(groups) self.image = pygame.Surface((4, 20)) self.image.fill('yellow') self.rect = self.image.get_rect(midbottom=pos) self.speed = -10 def update(self): self.rect.y += self.speed if self.rect.bottom < 0: self.kill() # ทำลายตัวเองเมื่อออกนอกจอ # ใน Player Class เพิ่ม: def shoot(self): # Player สร้าง Laser แต่ Laser จัดการการเคลื่อนที่เอง Laser(self.rect.midtop, self.groups)
class Enemy(pygame.sprite.Sprite): def __init__(self, pos, type, groups): super().__init__(groups) if type == 'A': self.image = ... self.speed = 2 elif type == 'B': self.image = ... self.speed = 5 self.rect = self.image.get_rect(topleft=pos)
class EnemyFactory: @staticmethod def spawn(groups): x = random.randint(0, 800) y = random.randint(-100, -40) type = random.choice(['A', 'B']) return Enemy((x,y), type, groups)
แยกตรรกะการชนออกจากตัว Sprite (Mediator/Manager)
class CollisionManager: def __init__(self, player, enemy_group, laser_group): self.player = player self.enemies = enemy_group self.lasers = laser_group def check(self): # Laser ชน Enemy if pygame.sprite.groupcollide(self.lasers, self.enemies, True, True): # ส่ง Event หรือเพิ่มคะแนนที่นี่ print("Enemy Destroyed") # Enemy ชน Player if pygame.sprite.spritecollide(self.player, self.enemies, True): print("Player Hit!") self.player.take_damage()
class ScoreManager: _instance = None # Singleton Pattern (แบบง่าย) def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance.score = 0 cls._instance.font = pygame.font.Font(None, 36) return cls._instance def add(self, amount): self.score += amount def draw(self, surface): surf = self.font.render(f'Score: {self.score}', True, 'white') surface.blit(surf, (10, 10))
กลับไปที่คลาส Game และเชื่อมต่อทุกอย่าง
class Game: def __init__(self): # ... init pygame ... self.all_sprites = pygame.sprite.Group() self.enemy_group = pygame.sprite.Group() self.laser_group = pygame.sprite.Group() # สร้าง Player self.player = Player([self.all_sprites]) self.player.laser_group = self.laser_group # Dependency Injection # Managers self.collision = CollisionManager(self.player, self.enemy_group, self.laser_group) self.score_manager = ScoreManager() # ตั้งเวลา Spawn ศัตรู (Event) self.ENEMY_SPAWN = pygame.USEREVENT + 1 pygame.time.set_timer(self.ENEMY_SPAWN, 1000) def handle_input(self): for event in pygame.event.get(): if event.type == self.ENEMY_SPAWN: enemy = EnemyFactory.spawn([self.all_sprites, self.enemy_group]) def update(self): self.all_sprites.update() self.collision.check() def draw(self): self.screen.fill('black') self.all_sprites.draw(self.screen) self.score_manager.draw(self.screen) pygame.display.flip()
เพื่อให้เกมลื่นไหลในทุกเฟรมเรท
dt = self.clock.tick() / 1000
self.rect.x += self.speed * dt
แทนที่จะ kill() และสร้างใหม่ตลอดเวลา ให้นำกระสุนที่ใช้นำกลับมาใช้ใหม่ (Reuse) ช่วยลดภาระ Garbage Collector
สร้างคลาส Boss ที่สืบทอดจาก Enemy โดยมี HP และยิงกระสุนสวนกลับได้ ห้ามแก้โค้ด Game Loop
สร้างคลาส WeaponStrategy ให้ Player เปลี่ยนรูปแบบการยิง (เดี่ยว, กระจาย) ได้ขณะเล่น
"Design Patterns ไม่ใช่กฎข้อบังคับ แต่เป็นเครื่องมือช่วยแก้ปัญหา"