前回に続いてPythonでゲームを作っていきます。
前回までに作ったプログラムをきれいにしましたので、以下のプログラムからスタートしたいと思います。
こちらのコードを活用してテトリスのような落ちもの系パズルゲームを作っていきます。
"""落ちものゲームの土台:グリッド表示+1マスのスプライト
中学生向けに、読みやすいコメントと構成にしています。
- 画面左側に白い盤面(10x20マス)を描画
- キー操作で1マス(スプライト)を上下左右に移動
"""
from __future__ import annotations
import sys
import pygame
# ============================================================================
# 定数(ゲーム全体で使う値)
# ============================================================================
FPS = 60 # 1秒あたりの描画回数(フレームレート)
CELL = 30 # 1マスの大きさ(ピクセル)
COLS, ROWS = 10, 20 # 盤面の列数(横)、行数(縦)
PANEL_WIDTH = 240 # 右側の情報パネルの幅(ピクセル)
# 画面サイズ(横幅は「盤面の横幅 + パネル幅」)
WIDTH = CELL * COLS + PANEL_WIDTH
HEIGHT = CELL * ROWS
# 色(RGB)
WHITE = (255, 255, 255)
GRAY = (200, 200, 200)
BLACK = (0, 0, 0)
BLUE = (0, 120, 255)
# ============================================================================
# クラス定義
# ============================================================================
class CellBlock(pygame.sprite.Sprite):
"""盤面上の『1マス』を表すスプライト。
grid_x, grid_y は「マスの座標」(ピクセルではない点に注意)。
画面上のピクセル位置は update_rect() で計算して rect に反映します。
"""
def __init__(self, grid_x: int, grid_y: int, color: tuple[int, int, int], cell_size: int) -> None:
super().__init__()
# -1 するのは、グリッド線と重なって見えづらくならないための微調整
self.image = pygame.Surface((cell_size - 1, cell_size - 1))
self.image.fill(color)
self.rect = self.image.get_rect()
self.grid_x = grid_x
self.grid_y = grid_y
self.cell_size = cell_size
self.update_rect()
def update_rect(self) -> None:
"""グリッド座標(マス)→ 画面上のピクセル座標に変換して配置する。"""
self.rect.topleft = (self.grid_x * self.cell_size, self.grid_y * self.cell_size)
# ============================================================================
# 描画用関数
# ============================================================================
def draw_board(screen: pygame.Surface) -> None:
"""左側の盤面(白背景+マス目の線)を描画する。"""
# 盤面の白い背景(左側だけを塗る)
screen.fill(WHITE, (0, 0, CELL * COLS, HEIGHT))
# 薄いグリッド線(縦線)
for x in range(COLS + 1):
pygame.draw.line(screen, GRAY, (x * CELL, 0), (x * CELL, HEIGHT))
# 薄いグリッド線(横線)
for y in range(ROWS + 1):
pygame.draw.line(screen, GRAY, (0, y * CELL), (CELL * COLS, y * CELL))
# ============================================================================
# メイン処理
# ============================================================================
def main() -> None:
"""ゲームの初期化・イベント処理・描画を行うメインループ。"""
pygame.init()
pygame.display.set_caption("落ちゲー")
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
# スプライトのグループ(描画や更新をまとめて行える)
all_blocks = pygame.sprite.Group()
# 例:中央付近(x=4, y=2)に青いブロックを1つ置く
block = CellBlock(grid_x=4, grid_y=2, color=BLUE, cell_size=CELL)
all_blocks.add(block)
running = True
while running:
dt = clock.tick(FPS) # 経過ミリ秒(将来、落下速度などに使える)
for event in pygame.event.get():
# ウィンドウ右上の「×」ボタンで終了
if event.type == pygame.QUIT:
running = False
# キーが押されたとき:1マスずつ動かす(盤面の外に出ないように制限)
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT and block.grid_x > 0:
block.grid_x -= 1
block.update_rect()
elif event.key == pygame.K_RIGHT and block.grid_x < COLS - 1:
block.grid_x += 1
block.update_rect()
elif event.key == pygame.K_UP and block.grid_y > 0:
block.grid_y -= 1
block.update_rect()
elif event.key == pygame.K_DOWN and block.grid_y < ROWS - 1:
block.grid_y += 1
block.update_rect()
# 背景を濃いグレーで塗り、盤面(白+グリッド)を描く
screen.fill((30, 30, 30))
draw_board(screen)
# ブロック(スプライト)を描画
all_blocks.draw(screen)
# 画面に反映
pygame.display.flip()
# 後片付け
pygame.quit()
sys.exit()
# スクリプトとして直接実行されたときだけ main() を呼ぶ
if __name__ == "__main__":
main()
ステップ1 1マスのスプライトを登場させる
ステップ2 4マスのピースを登場させる
1) 形データと色の候補を追加
(ファイル先頭の定数付近に追記。import random も忘れずに)
# ★先頭のインポート部分に以下のコードを追加
import random
# ★定数部分に以下のコードを追加
COLORS = [
(0,180,255), (255,200,0), (170,0,255),
(0,200,80), (255,100,0), (255,0,100), (0,120,255)
]
# 4つの相対座標で形を表現(テトリスの7種)
SHAPES = {
"I": [(-2,0),(-1,0),(0,0),(1,0)],
"O": [(-1,0),(0,0),(-1,1),(0,1)],
"T": [(-1,0),(0,0),(1,0),(0,1)],
"S": [(0,0),(1,0),(-1,1),(0,1)],
"Z": [(-1,0),(0,0),(0,1),(1,1)],
"J": [(-1,0),(-1,1),(0,0),(1,0)],
"L": [(-1,0),(0,0),(1,0),(1,1)],
}2) ピース(4マスの束)クラスを追加
CellBlock を4つ内包し、移動と回転(簡易壁キック付き)ができます。
class Piece:
def __init__(self, kind=None, color=None):
self.kind = kind or random.choice(list(SHAPES.keys()))
self.color = color or random.choice(COLORS)
self.x = COLS // 2 # 盤面中央から出現
self.y = 1
self.offsets = [list(p) for p in SHAPES[self.kind]] # 形の相対座標
# 4つのCellBlockを作ってグループに入れる
self.blocks = []
self.group = pygame.sprite.Group()
for (ox, oy) in self.offsets:
b = CellBlock(self.x + ox, self.y + oy, self.color, CELL)
self.blocks.append(b)
self.group.add(b)
def world_cells(self):
return [(self.x + ox, self.y + oy) for (ox, oy) in self.offsets]
def _hits_wall(self):
# 盤面の外に出ていないか(まだ固定ブロックとの衝突は見ない)
for (gx, gy) in self.world_cells():
if not (0 <= gx < COLS and 0 <= gy < ROWS):
return True
return False
def update_sprites(self):
# grid座標 → 画面座標に反映
for i, (ox, oy) in enumerate(self.offsets):
self.blocks[i].grid_x = self.x + ox
self.blocks[i].grid_y = self.y + oy
self.blocks[i].update_rect()
def move(self, dx, dy):
old_x, old_y = self.x, self.y
self.x += dx; self.y += dy
if self._hits_wall():
self.x, self.y = old_x, old_y
return False
self.update_sprites()
return True
def rotate_cw(self):
# 時計回り (x,y)->(-y,x)、壁キック(左右に少しずらして試す)
return self._rotate([[-oy, ox] for (ox, oy) in self.offsets])
def rotate_ccw(self):
# 反時計回り (x,y)->(y,-x)
return self._rotate([[oy, -ox] for (ox, oy) in self.offsets])
def _rotate(self, new_offsets):
old_offsets, old_x = self.offsets, self.x
self.offsets = new_offsets
for dx in [0, -1, 1, -2, 2]:
self.x = old_x + dx
if not self._hits_wall():
self.update_sprites()
return True
# 無理なら元に戻す
self.offsets, self.x = old_offsets, old_x
return False
def draw(self, screen):
self.group.draw(screen)
3) main() を少し変更
- これまでの「単体ブロック」は不要にして、ピースを1つ出す
- 矢印キーで移動、
↑とZで回転
def main():
pygame.init()
pygame.display.set_caption("落ちゲー")
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
# いずれ固定ブロックを入れるためのグループ(今は空)
fixed_blocks = pygame.sprite.Group()
# 今落ちているピースを1つ生成
current = Piece() # 種類はランダム
running = True
while running:
dt = clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
current.move(-1, 0)
elif event.key == pygame.K_RIGHT:
current.move(1, 0)
elif event.key == pygame.K_DOWN:
current.move(0, 1)
elif event.key == pygame.K_UP:
current.rotate_cw()
elif event.key == pygame.K_z:
current.rotate_ccw()
screen.fill((30, 30, 30))
draw_board(screen)
# (将来用)固定ブロックを描画
fixed_blocks.draw(screen)
# 現在のピースを描画
current.draw(screen)
pygame.display.flip()
pygame.quit()
sys.exit()
動作チェック
- 4マスのピースが中央に出現
- ←→↓ で動く/↑とZで回転(端っこで回転しても簡単な壁キックで入る)
次は「自動で落ちる(重力)→止まったら固定する → 新しいピースが出る」を実装します。