【Python】横スクロールゲームを作ってみた(初心者プログラミング)
はようございます。タカヒデです。
本日はPythonで「横スクロールゲーム」を作ってみました。
STEP形式で解説しているので、「まずは何かを作ってみたい」という初心者の方の参考になれば幸いです。
- プログラミング初心者
- 何から始めればよいか分からない
- まずは簡単なゲームを作って興味を持ってみたい
ぜひ実際にコードを打ちながら作成してみてください。
「横スクロールゲーム」完成イメージ
まずは、「横スクロールゲーム」完成後の最終的なコードと完成イメージです。
import pygame
import sys
import random
# =====================================
# 設定(画面サイズ・色など)
# =====================================
WIDTH = 800
HEIGHT = 400
FPS = 60
GROUND_Y = 300
PLAYER_WIDTH = 40
PLAYER_HEIGHT = 40
PLAYER_X = 100
GRAVITY = 0.6
# ジャンプ調整
JUMP_POWER_MIN = -8
JUMP_POWER_MAX = -18
MAX_CHARGE_MS = 400
# 障害物のランダムサイズ
OBSTACLE_MIN_WIDTH = 20
OBSTACLE_MAX_WIDTH = 50
OBSTACLE_MIN_HEIGHT = 30
OBSTACLE_MAX_HEIGHT = 80
# 難易度
BASE_OBSTACLE_SPEED = 5
MAX_OBSTACLE_SPEED = 12
BASE_INTERVAL = 1500
MIN_INTERVAL = 800
# =====================================
# 難易度計算系
# =====================================
def calc_obstacle_speed(passed_time_ms: int) -> int:
speed = BASE_OBSTACLE_SPEED + passed_time_ms // 5000
return min(speed, MAX_OBSTACLE_SPEED)
def calc_spawn_interval(passed_time_ms: int) -> int:
interval = BASE_INTERVAL - (passed_time_ms // 3000) * 100
return max(interval, MIN_INTERVAL)
# =====================================
# 初期化
# =====================================
def reset_game():
player_y = GROUND_Y - PLAYER_HEIGHT
player_vy = 0
is_jumping = False
charging_jump = False
jump_charge = 0
obstacles = []
score = 0
obstacle_timer = 0
start_ticks = pygame.time.get_ticks()
return (
player_y,
player_vy,
is_jumping,
charging_jump,
jump_charge,
obstacles,
score,
obstacle_timer,
start_ticks,
)
# =====================================
# メイン処理
# =====================================
def main():
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("横スクロールゲーム")
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 30)
(
player_y,
player_vy,
is_jumping,
charging_jump,
jump_charge,
obstacles,
score,
obstacle_timer,
start_ticks,
) = reset_game()
game_over = False
while True:
dt = clock.tick(FPS)
# ---------------------
# 入力
# ---------------------
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and not game_over:
if not is_jumping and not charging_jump:
charging_jump = True
jump_charge = 0
if event.key == pygame.K_SPACE and game_over:
(
player_y,
player_vy,
is_jumping,
charging_jump,
jump_charge,
obstacles,
score,
obstacle_timer,
start_ticks,
) = reset_game()
game_over = False
if event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE and charging_jump and not game_over:
ratio = min(jump_charge / MAX_CHARGE_MS, 1)
jump_power = JUMP_POWER_MIN + (JUMP_POWER_MAX - JUMP_POWER_MIN) * ratio
player_vy = jump_power
is_jumping = True
charging_jump = False
# ---------------------
# 更新
# ---------------------
if not game_over:
passed_time = pygame.time.get_ticks() - start_ticks
obstacle_speed = calc_obstacle_speed(passed_time)
score += 1
if charging_jump:
jump_charge = min(jump_charge + dt, MAX_CHARGE_MS)
player_vy += GRAVITY
player_y += player_vy
if player_y >= GROUND_Y - PLAYER_HEIGHT:
player_y = GROUND_Y - PLAYER_HEIGHT
player_vy = 0
is_jumping = False
obstacle_timer += dt
interval = calc_spawn_interval(passed_time)
if obstacle_timer >= interval:
obstacle_timer = 0
w = random.randint(OBSTACLE_MIN_WIDTH, OBSTACLE_MAX_WIDTH)
h = random.randint(OBSTACLE_MIN_HEIGHT, OBSTACLE_MAX_HEIGHT)
x = WIDTH + random.randint(0, 100)
y = GROUND_Y - h
obstacles.append(pygame.Rect(x, y, w, h))
new_obstacles = []
player_rect = pygame.Rect(PLAYER_X, player_y, PLAYER_WIDTH, PLAYER_HEIGHT)
for obs in obstacles:
obs.x -= obstacle_speed
if obs.right > 0:
new_obstacles.append(obs)
if obs.colliderect(player_rect):
game_over = True
charging_jump = False
obstacles = new_obstacles
# ---------------------
# 描画
# ---------------------
screen.fill((30, 30, 50))
pygame.draw.line(screen, (200, 200, 200), (0, GROUND_Y), (WIDTH, GROUND_Y), 2)
pygame.draw.rect(screen, (100, 200, 100), (PLAYER_X, player_y, PLAYER_WIDTH, PLAYER_HEIGHT))
for obs in obstacles:
pygame.draw.rect(screen, (200, 100, 100), obs)
score_text = font.render(f"Score: {score // 10}", True, (255, 255, 255))
screen.blit(score_text, (10, 10))
info_text = font.render("Short press: low jump / Long press: high jump", True, (255, 255, 255))
screen.blit(info_text, (10, HEIGHT - 35))
if game_over:
msg1 = font.render("Game Over", True, (255, 255, 255))
msg2 = font.render("Press SPACE to Restart", True, (255, 255, 255))
screen.blit(msg1, (WIDTH // 2 - msg1.get_width() // 2, HEIGHT // 2 - 40))
screen.blit(msg2, (WIDTH // 2 - msg2.get_width() // 2, HEIGHT // 2))
pygame.display.flip()
if __name__ == "__main__":
main()
こんなゲームが作れます。

STEP1:ゲームウィンドウを表示する
まずは、横スクロールゲームの土台となる「ウィンドウ表示」と「ずっと動き続けるゲームのループ」を作ります。
ここではまだプレイヤーも障害物も出てきません。
画面が開いて、暗い背景色のウィンドウが表示されるところまでを目標にします。
import pygame
import sys
# =====================================
# 設定(画面サイズ・色など)
# =====================================
WIDTH = 800
HEIGHT = 400
FPS = 60
# =====================================
# メイン処理
# =====================================
def main():
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("横スクロールゲーム")
clock = pygame.time.Clock()
while True:
dt = clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill((30, 30, 50))
pygame.display.flip()
if __name__ == "__main__":
main()
手順①:Pygameを使う準備(importと設定)
import pygame
import sys
# =====================================
# 設定(画面サイズ・色など)
# =====================================
WIDTH = 800
HEIGHT = 400
FPS = 60
「import」は「別のモジュールを読み込む」という意味です。
- 「pygame」
ゲームを作りやすくするためのライブラリ - 「sys」
システムとやり取りするためのライブラリ
その下の「設定」のブロックでは、画面の基本的な大きさや動く速さを決めています。
- 「WIDTH = 800」
ウィンドウの横幅 - 「HEIGHT = 400」
ウィンドウの縦幅 - 「FPS = 60」
1秒間に何回画面を描き直すか
こうした「設定用の変数」を最初にまとめて書いておくと、あとでゲームのサイズやスピードを変えたいときに、とても楽になります。
手順②:main関数とゲームループを作る
def main(): # ★STEP1追加
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("横スクロールゲーム")
clock = pygame.time.Clock()
while True:
dt = clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
次に、ゲームの中心となる「main」関数と、その中の「ゲームループ」を見ていきます。
「def main():」で「mainという名前の関数」を作っています。
その中でやっていることは次の通りです。
- 「pygame.init()」
Pygameを使う前の初期化処理 - 「screen = pygame.display.set_mode((WIDTH, HEIGHT))」
「set_mode」に画面サイズ(幅・高さ)を渡し、ゲーム画面を作成 - 「pygame.display.set_caption(“横スクロールゲーム”)」
ウィンドウのタイトルバーに表示される文字を設定 - 「clock = pygame.time.Clock()」
ゲームの動く速さを調整するためのオブジェクト設定
while True:
dt = clock.tick(FPS)
その下の記載は「無限ループ」です。
- 「while True:」
「True」の間ずっと繰り返す、という意味でゲームが続く限り処理がずっと回り続ける - 「dt = clock.tick(FPS)」
「次の1フレームまで待つ」処理
FPSを指定することで「1秒間に最大60回だけループが回る」ようにして、ゲームの動きを一定に保つ
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
その次の部分では、ウィンドウの×ボタンが押されたかどうかをチェックしています。
こうして、「ウィンドウを閉じたらちゃんとゲームが終わる」ようになっています。
手順③:画面を塗りつぶして表示する
screen.fill((30, 30, 50))
pygame.display.flip()
画面に色をつけて表示する部分です。
- 「screen.fill((30, 30, 50))」
画面全体を一色で塗りつぶす - 「pygame.display.flip()」
実際に画面に描いた内容を表示
手順④:mainを起動する
if __name__ == "__main__": # ★STEP1追加
main()
「if name == “main”:」は「このファイルが直接実行されたときだけ、下の処理を動かす」という意味になり、その中で「main()」を呼ぶことで、「ゲームのメイン処理」をスタートさせています。
こうして、
- ファイルを実行する
- 「main()」が呼ばれる
- ウィンドウが開き、ゲームループが回り続ける
という流れが作られています。
STEP1が完了した時点では以下のアプリができています。

STEP2:地面とプレイヤーを表示する
次に、「横スクロールゲーム」の主役となるプレイヤーと、足場となる地面を画面に表示します。
import pygame
import sys
# =====================================
# 設定(画面サイズ・色など)
# =====================================
WIDTH = 800
HEIGHT = 400
FPS = 60
# ★STEP2追加
GROUND_Y = 300
PLAYER_WIDTH = 40
PLAYER_HEIGHT = 40
PLAYER_X = 100
# =====================================
# メイン処理
# =====================================
def main():
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("横スクロールゲーム")
clock = pygame.time.Clock()
# ★STEP2追加
player_y = GROUND_Y - PLAYER_HEIGHT
while True:
dt = clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill((30, 30, 50))
# ★STEP2追加
pygame.draw.line(screen, (200, 200, 200), (0, GROUND_Y), (WIDTH, GROUND_Y), 2)
pygame.draw.rect(screen, (100, 200, 100), (PLAYER_X, player_y, PLAYER_WIDTH, PLAYER_HEIGHT))
pygame.display.flip()
if __name__ == "__main__":
main()
手順①:地面の高さを決める定数を追加する
GROUND_Y = 300
PLAYER_WIDTH = 40
PLAYER_HEIGHT = 40
PLAYER_X = 100
ここでは、プレイヤーや地面の位置・サイズに関する「数字」をまとめて変数として定義しています。
- 「GROUND_Y」
地面の高さを表す値 - 「PLAYER_WIDTH」「PLAYER_HEIGHT」
プレイヤー(四角形)の幅と高さ - 「PLAYER_X」
プレイヤーの横方向の位置
ゲーム中、プレイヤーは横方向には動かず、決めた位置に固定しておく
手順②:プレイヤーの縦位置を計算しておく
player_y = GROUND_Y - PLAYER_HEIGHT
「player_y」はプレイヤーの縦方向の位置です。
- 「GROUND_Y」
地面の高さ - 「PLAYER_HEIGHT」
プレイヤーの高さ
なので、プレイヤーを「地面の上にちょうど乗せる」ために、「GROUND_Y – PLAYER_HEIGHT」としています。
手順③:地面の線を描画する
pygame.draw.line(screen, (200, 200, 200), (0, GROUND_Y), (WIDTH, GROUND_Y), 2)
「pygame.draw.line」は「線を描く」ための命令です。
それぞれの引数は次の意味があります。
- 第1引数「screen」
線を描く対象の画面を指定 - 第2引数「(200, 200, 200)」
「(R, G, B)」形式の線の色 - 第3引数「(0, GROUND_Y)」
線の始点の座標 - 第4引数「(WIDTH, GROUND_Y)」
線の終点の座標 - 第5引数「2」
線の太さ(ピクセル数)
この一行で、「画面の左から右まで、地面のラインを一本引く」ことができます。
手順④:プレイヤーの四角形を描画する
pygame.draw.rect(screen, (100, 200, 100), (PLAYER_X, player_y, PLAYER_WIDTH, PLAYER_HEIGHT))
「pygame.draw.rect」は「四角形を描く」ための命令です。
- 第1引数「screen」
描画する画面 - 第2引数「(100, 200, 100)」
四角形の塗りつぶしの色 - 第3引数「(PLAYER_X, player_y, PLAYER_WIDTH, PLAYER_HEIGHT)」
四角形の位置と大きさ
これによって、「左側に立っているプレイヤー」の見た目が完成します。
STEP2が完了した時点では以下のアプリができています。

STEP3:プレイヤーをジャンプさせる
このSTEPでは、スペースキーを押したときにプレイヤーがジャンプできるようにします。
まずは「単純なジャンプ(一定の力で飛ぶ)」を実装し、ゲームらしい動きを作っていきましょう。
import pygame
import sys
# =====================================
# 設定(画面サイズ・色など)
# =====================================
WIDTH = 800
HEIGHT = 400
FPS = 60
GROUND_Y = 300
PLAYER_WIDTH = 40
PLAYER_HEIGHT = 40
PLAYER_X = 100
# ★STEP3追加
GRAVITY = 0.6
JUMP_POWER = -12
# =====================================
# メイン処理
# =====================================
def main():
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("横スクロールゲーム")
clock = pygame.time.Clock()
player_y = GROUND_Y - PLAYER_HEIGHT
# ★STEP3追加
player_vy = 0
is_jumping = False
while True:
dt = clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# ★STEP3追加
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if not is_jumping:
player_vy = JUMP_POWER
is_jumping = True
# ★STEP3追加:ジャンプの物理計算
player_vy += GRAVITY
player_y += player_vy
# ★STEP3追加:地面の上で止める
if player_y >= GROUND_Y - PLAYER_HEIGHT:
player_y = GROUND_Y - PLAYER_HEIGHT
player_vy = 0
is_jumping = False
screen.fill((30, 30, 50))
pygame.draw.line(screen, (200, 200, 200), (0, GROUND_Y), (WIDTH, GROUND_Y), 2)
pygame.draw.rect(screen, (100, 200, 100), (PLAYER_X, player_y, PLAYER_WIDTH, PLAYER_HEIGHT))
pygame.display.flip()
if __name__ == "__main__":
main()
手順①:ジャンプに必要な「重力」と「初速」を設定する
GRAVITY = 0.6 #重力の値
JUMP_POWER = -12 #ジャンプの初速
まずは設定部分の追加を見てみましょう。
ジャンプを自然に見せるためには、物理の考え方を小さく取り入れます。
- 「GRAVITY」
毎フレームごとにどれだけ下向きに加速させるかを表す値
数値が大きいほど、落下が速くなっていく - 「JUMP_POWER」
ジャンプのときに一度だけ「上向き」に加える力の大きさ
上がマイナス方向なので、負の値になる
この2つがあることで、「上に飛んで、だんだん落ちる」という自然な動きが作れます。
手順②:プレイヤーの速度とジャンプ状態を管理する変数を追加する
player_vy = 0 #プレイヤーの縦方向の速度
is_jumping = False #ジャンプ中かどうかの判定
- 「player_vy」
プレイヤーの縦方向の動く速さを表す変数 - 「is_jumping」
「今ジャンプ中かどうか」を判定するフラグ
地面にいるときだけ新しいジャンプができるようにするために使う
手順③:スペースキーでジャンプさせる
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if not is_jumping: # 地面にいるときだけジャンプ
player_vy = JUMP_POWER
is_jumping = True
やっていることは次の3つです。
- キーボードのキーが押されたかをチェック
- 押されたキーがスペースキーかどうかをチェック
- 「is_jumping」がFalse(地面にいる)ならジャンプさせる
ジャンプするときの処理は、
- 「player_vy」にジャンプの初速(JUMP_POWER)を入れる
- 「is_jumping」をTrueにして、空中にいると印をつけておく
の2つだけです。
手順④:重力をかける、落ちる、地面で止める
player_vy += GRAVITY
player_y += player_vy
- 毎フレーム「player_vy」に重力を足す
- その速度だけ「player_y(位置)」を動かす
これによって、「上に飛んだあと、だんだん落ちる」という動きが生まれます。
if player_y >= GROUND_Y - PLAYER_HEIGHT:
player_y = GROUND_Y - PLAYER_HEIGHT
player_vy = 0
is_jumping = False
次に地面での処理です。
「地面より下に行きそうなら、位置を地面の上に戻して止める」という処理です。
- 「player_y」を地面の上に戻す
- 「player_vy」速度を0にして動きを止める
- 「is_jumping」をFalseにして、次のジャンプを許可する
という役割があります。
STEP3が完了した時点で、プレイヤーを動かすことができるようになりました。

STEP4:障害物を流してゲームオーバーを作る
このSTEPでは、右から左に流れてくる障害物を追加し、プレイヤーがぶつかったらゲームオーバーになる仕組みを作ります。
まだスコアや難易度調整は入れず、「避ける対象」が登場するところまでを目標にします。
import pygame
import sys
# =====================================
# 設定(画面サイズ・色など)
# =====================================
WIDTH = 800
HEIGHT = 400
FPS = 60
GROUND_Y = 300
PLAYER_WIDTH = 40
PLAYER_HEIGHT = 40
PLAYER_X = 100
GRAVITY = 0.6
JUMP_POWER = -12
# ★STEP4追加:障害物のサイズと速さ
OBSTACLE_WIDTH = 30
OBSTACLE_HEIGHT = 50
OBSTACLE_SPEED = 5
# =====================================
# メイン処理
# =====================================
def main():
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("横スクロールゲーム")
clock = pygame.time.Clock()
player_y = GROUND_Y - PLAYER_HEIGHT
player_vy = 0
is_jumping = False
# ★STEP4追加:障害物
obstacles = [] #障害物のリスト
obstacle_timer = 0 #障害物を出すタイミング管理用
game_over = False #ゲームオーバーかどうか
while True:
dt = clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if not is_jumping and not game_over:
player_vy = JUMP_POWER
is_jumping = True
# ★STEP4変更
if not game_over:
player_vy += GRAVITY
player_y += player_vy
if player_y >= GROUND_Y - PLAYER_HEIGHT:
player_y = GROUND_Y - PLAYER_HEIGHT
player_vy = 0
is_jumping = False
# ★STEP4追加:一定時間ごとに障害物を出す
obstacle_timer += dt
if obstacle_timer >= 1500:
obstacle_timer = 0
obstacle_x = WIDTH
obstacle_y = GROUND_Y - OBSTACLE_HEIGHT
obstacles.append(pygame.Rect(obstacle_x, obstacle_y, OBSTACLE_WIDTH, OBSTACLE_HEIGHT))
new_obstacles = []
player_rect = pygame.Rect(PLAYER_X, player_y, PLAYER_WIDTH, PLAYER_HEIGHT)
for obs in obstacles:
obs.x -= OBSTACLE_SPEED
if obs.right > 0:
new_obstacles.append(obs)
if obs.colliderect(player_rect): # ぶつかったらゲームオーバー
game_over = True
obstacles = new_obstacles
#ここまで「一定時間ごとに障害物を出す」の追加箇所
screen.fill((30, 30, 50))
pygame.draw.line(screen, (200, 200, 200), (0, GROUND_Y), (WIDTH, GROUND_Y), 2)
pygame.draw.rect(screen, (100, 200, 100), (PLAYER_X, player_y, PLAYER_WIDTH, PLAYER_HEIGHT))
# ★STEP4追加:障害物の描画
for obs in obstacles:
pygame.draw.rect(screen, (200, 100, 100), obs)
pygame.display.flip()
if __name__ == "__main__":
main()
手順①:障害物のサイズと速さを設定する
OBSTACLE_WIDTH = 30
OBSTACLE_HEIGHT = 50
OBSTACLE_SPEED = 5
まずは、障害物の見た目と動きに関する定数を追加しました。
それぞれの意味は次の通りです。
- 「OBSTACLE_WIDTH」
障害物の横幅 - 「OBSTACLE_HEIGHT」
障害物の縦の高さ - 「OBSTACLE_SPEED」
障害物が左に向かって動く速さ
手順②:障害物を管理するための変数を追加する
obstacles = [] # 障害物のリスト
obstacle_timer = 0 # 障害物を出すタイミング管理用
game_over = False # ゲームオーバーかどうか
次に、メイン関数の中で、障害物を管理する変数を用意しています。ここで出てくる考え方は次の3つです。
- 「obstacles」
複数の障害物をまとめて管理するためのリスト
1つ1つの障害物は「Rect」として扱い、それをこのリストに追加していく - 「obstacle_timer」
「次の障害物を出すまでに経過した時間」を数えるための変数
一定時間がたったら新しい障害物を作る、という仕組みに使う - 「game_over」
ゲームオーバー状態かどうかを表すフラグ
Trueになったら、プレイヤーや障害物の更新を止める
手順③:ゲームオーバー時は更新を止める
if not game_over:
player_vy += GRAVITY
player_y += player_vy
if player_y >= GROUND_Y - PLAYER_HEIGHT:
player_y = GROUND_Y - PLAYER_HEIGHT
player_vy = 0
is_jumping = False
...
ゲームオーバー時は更新を止める処理です。
- 「game_over」が「False」のときだけ、プレイヤーの位置や障害物を動かす
- 「game_over」が「True」になったら、物理計算や移動を一切しない
という制御です。
手順④:一定時間ごとに障害物を出す
obstacle_timer += dt
if obstacle_timer >= 1500:
obstacle_timer = 0
obstacle_x = WIDTH
obstacle_y = GROUND_Y - OBSTACLE_HEIGHT
obstacles.append(pygame.Rect(obstacle_x, obstacle_y, OBSTACLE_WIDTH, OBSTACLE_HEIGHT))
障害物を出すタイミングは、「経過時間」を使って管理しています。
- 「dt」
「clock.tick(FPS)」が返してくれる「前のフレームからの経過時間(ミリ秒)」 - 「obstacle_timer += dt」
1フレームごとに、その経過時間を足し込んでいく
つまり「ゲーム開始からこれまでに何ミリ秒たったか」ではなく、「最後に障害物を出してからどれくらいたったか」を測る - 「if obstacle_timer >= 1500」
1500ミリ秒、つまり1.5秒たったら、新しい障害物を1つ追加する - 「pygame.Rect(obstacle_x, obstacle_y, OBSTACLE_WIDTH, OBSTACLE_HEIGHT)」
新しい障害物の四角形オブジェクトを作る - 「obstacles.append」
作った障害物をリストに追加し、ゲームループの中でまとめて扱えるようにする
手順⑤:障害物を動かして、衝突したらゲームオーバーにする
new_obstacles = []
player_rect = pygame.Rect(PLAYER_X, player_y, PLAYER_WIDTH, PLAYER_HEIGHT)
for obs in obstacles:
obs.x -= OBSTACLE_SPEED
if obs.right > 0:
new_obstacles.append(obs)
if obs.colliderect(player_rect):
game_over = True
obstacles = new_obstacles
最後に、障害物の移動と衝突判定です。
- 「player_rect」
プレイヤーの当たり判定用の四角形 - 「for obs in obstacles」
画面に存在するすべての障害物について、1つずつ処理 - 「obs.x -= OBSTACLE_SPEED」
障害物のx座標(横方向の位置)を少しずつ左に動かす - 「if obs.right > 0:」
「obs.right」は障害物の右端のx座標
つまりまだ画面に少しでも見えている場合だけ「new_obstacles」に残す - 「if obs.colliderect(player_rect):」
「colliderect」は、2つの四角形が重なっているかどうかを判定する関数
「ぶつかっていれば True、ぶつかっていなければ False」を返す
衝突していたら「game_over = True」にして、それ以降の更新処理を止める
STEP4が完了した時点で「プレイヤーが障害物に当たった瞬間にゲームオーバー」という基本的なルールが完成しました。

STEP5:最終版の完成とリファクタリング
このSTEPでは、最初に作成した完成版と同じ状態まで一気に仕上げます。
主な内容は次の通りです。
- スペースキーの長押しでジャンプの高さを調整できるようにする
- 障害物の大きさをランダムにして単調さをなくす
- 時間が経つと障害物が速くなる・出現間隔が短くなる「難易度アップ」
- スコア表示と、Game Over後にスペースキーでリスタート
- ゲーム全体の状態を関数にまとめるリファクタリング
import pygame
import sys
# ★STEP5追加:ランダムな値を使うためのモジュール
import random
# =====================================
# 設定(画面サイズ・色など)
# =====================================
WIDTH = 800
HEIGHT = 400
FPS = 60
GROUND_Y = 300
PLAYER_WIDTH = 40
PLAYER_HEIGHT = 40
PLAYER_X = 100
GRAVITY = 0.6
# ジャンプ調整(長押しでジャンプ力を変える) # ★STEP5修正・追加
JUMP_POWER_MIN = -8
JUMP_POWER_MAX = -18
MAX_CHARGE_MS = 400
# 障害物のランダムサイズ # ★STEP5修正・追加
OBSTACLE_MIN_WIDTH = 20
OBSTACLE_MAX_WIDTH = 50
OBSTACLE_MIN_HEIGHT = 30
OBSTACLE_MAX_HEIGHT = 80
# 難易度(スピードと出現間隔) # ★STEP5修正・追加
BASE_OBSTACLE_SPEED = 5
MAX_OBSTACLE_SPEED = 12
BASE_INTERVAL = 1500
MIN_INTERVAL = 800
# =====================================
# 難易度計算 # ★STEP5追加
# =====================================
def calc_obstacle_speed(passed_time_ms: int) -> int:
speed = BASE_OBSTACLE_SPEED + passed_time_ms // 5000
return min(speed, MAX_OBSTACLE_SPEED)
def calc_spawn_interval(passed_time_ms: int) -> int:
interval = BASE_INTERVAL - (passed_time_ms // 3000) * 100
return max(interval, MIN_INTERVAL)
# =====================================
# 初期化処理 # ★STEP5追加
# =====================================
def reset_game():
player_y = GROUND_Y - PLAYER_HEIGHT # メイン処理から持ってくる
player_vy = 0 # メイン処理から持ってくる
is_jumping = False # メイン処理から持ってくる
charging_jump = False
jump_charge = 0
obstacles = [] # メイン処理から持ってくる
score = 0
obstacle_timer = 0 # メイン処理から持ってくる
start_ticks = pygame.time.get_ticks()
return (
player_y,
player_vy,
is_jumping,
charging_jump,
jump_charge,
obstacles,
score,
obstacle_timer,
start_ticks,
)
# =====================================
# メイン処理
# =====================================
def main():
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("横スクロールゲーム")
clock = pygame.time.Clock()
# ★STEP5追加:文字描画用フォント
font = pygame.font.SysFont(None, 30)
# ★STEP5変更:初期化処理を関数からまとめて受け取る
(
player_y,
player_vy,
is_jumping,
charging_jump,
jump_charge,
obstacles,
score,
obstacle_timer,
start_ticks,
) = reset_game()
game_over = False
while True:
dt = clock.tick(FPS)
# ---------------------
# 入力処理
# ---------------------
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# スペースキーが押されたとき # ★STEP5修正
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and not game_over:
if not is_jumping and not charging_jump:
charging_jump = True # ジャンプ用のチャージ開始
jump_charge = 0
# ゲームオーバー時はスペースでリスタート # ★STEP5追加
if event.key == pygame.K_SPACE and game_over:
(
player_y,
player_vy,
is_jumping,
charging_jump,
jump_charge,
obstacles,
score,
obstacle_timer,
start_ticks,
) = reset_game()
game_over = False
# スペースキーを離したときにジャンプを確定させる # ★STEP5追加
if event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE and charging_jump and not game_over:
ratio = min(jump_charge / MAX_CHARGE_MS, 1)
jump_power = JUMP_POWER_MIN + (JUMP_POWER_MAX - JUMP_POWER_MIN) * ratio
player_vy = jump_power
is_jumping = True
charging_jump = False
# ---------------------
# 更新処理
# ---------------------
if not game_over:
# 経過時間+スコア加算 ★STEP5追加
passed_time = pygame.time.get_ticks() - start_ticks
obstacle_speed = calc_obstacle_speed(passed_time)
score += 1
# ジャンプチャージ(長押し時間をためる) # ★STEP5追加
if charging_jump:
jump_charge = min(jump_charge + dt, MAX_CHARGE_MS)
# ジャンプの物理計算(重力と位置更新)
player_vy += GRAVITY
player_y += player_vy
if player_y >= GROUND_Y - PLAYER_HEIGHT:
player_y = GROUND_Y - PLAYER_HEIGHT
player_vy = 0
is_jumping = False
# 障害物の出現タイミング # ★STEP5変更:時間に応じて間隔を変える
obstacle_timer += dt
interval = calc_spawn_interval(passed_time)
if obstacle_timer >= interval:
obstacle_timer = 0
w = random.randint(OBSTACLE_MIN_WIDTH, OBSTACLE_MAX_WIDTH)
h = random.randint(OBSTACLE_MIN_HEIGHT, OBSTACLE_MAX_HEIGHT)
x = WIDTH + random.randint(0, 100)
y = GROUND_Y - h
obstacles.append(pygame.Rect(x, y, w, h))
# 障害物の移動と当たり判定
new_obstacles = []
player_rect = pygame.Rect(PLAYER_X, player_y, PLAYER_WIDTH, PLAYER_HEIGHT)
for obs in obstacles:
obs.x -= obstacle_speed # ★STEP5修正
if obs.right > 0:
new_obstacles.append(obs)
if obs.colliderect(player_rect):
game_over = True
charging_jump = False # ★STEP5追加:チャージ中でもリセット
obstacles = new_obstacles
# ---------------------
# 描画処理
# ---------------------
screen.fill((30, 30, 50))
pygame.draw.line(screen, (200, 200, 200), (0, GROUND_Y), (WIDTH, GROUND_Y), 2)
pygame.draw.rect(screen, (100, 200, 100), (PLAYER_X, player_y, PLAYER_WIDTH, PLAYER_HEIGHT))
for obs in obstacles:
pygame.draw.rect(screen, (200, 100, 100), obs)
# スコア表示 # ★STEP5追加
score_text = font.render(f"Score: {score // 10}", True, (255, 255, 255))
screen.blit(score_text, (10, 10))
# 操作説明 # ★STEP5追加
info_text = font.render("Short press: low jump / Long press: high jump", True, (255, 255, 255))
screen.blit(info_text, (10, HEIGHT - 35))
# ゲームオーバー表示 # ★STEP5追加
if game_over:
msg1 = font.render("Game Over", True, (255, 255, 255))
msg2 = font.render("Press SPACE to Restart", True, (255, 255, 255))
screen.blit(msg1, (WIDTH // 2 - msg1.get_width() // 2, HEIGHT // 2 - 40))
screen.blit(msg2, (WIDTH // 2 - msg2.get_width() // 2, HEIGHT // 2))
pygame.display.flip()
if __name__ == "__main__":
main()
手順①:ジャンプ調整と障害物ランダム化のための設定
# ジャンプ調整(長押しでジャンプ力を変える)
JUMP_POWER_MIN = -8
JUMP_POWER_MAX = -18
MAX_CHARGE_MS = 400
# 障害物のランダムサイズ
OBSTACLE_MIN_WIDTH = 20
OBSTACLE_MAX_WIDTH = 50
OBSTACLE_MIN_HEIGHT = 30
OBSTACLE_MAX_HEIGHT = 80
まずは、設定部分を追加します。
- 「JUMP_POWER_MIN」
スペースキーを短く押したときのジャンプ力 - 「JUMP_POWER_MAX」
スペースキーを長く押したときのジャンプ力 - 「MAX_CHARGE_MS」
最大で何ミリ秒までジャンプの「ため」を受け付けるか、という上限値 - 「OBSTACLE_MIN/MAX_xxx」
障害物の幅と高さの「最小値と最大値」を決める変数
実際のサイズは「random.randint」でこの範囲からランダムに選ぶ
手順②:難易度計算と初期化を関数にまとめる
# =====================================
# 難易度計算 # ★STEP5追加
# =====================================
def calc_obstacle_speed(passed_time_ms: int) -> int:
speed = BASE_OBSTACLE_SPEED + passed_time_ms // 5000
return min(speed, MAX_OBSTACLE_SPEED)
def calc_spawn_interval(passed_time_ms: int) -> int:
interval = BASE_INTERVAL - (passed_time_ms // 3000) * 100
return max(interval, MIN_INTERVAL)
次に難易度計算を関数としてまとめます。
- 「passed_time_ms」
ゲーム開始からの経過時間(ミリ秒) - 「calc_obstacle_speed」
5秒ごとにスピードを1ずつ上げていく
速くなりすぎないように「MAX_OBSTACLE_SPEED」を上限に設定 - 「calc_spawn_interval」
3秒ごとに障害物の出現間隔を100msずつ短くする
頻度が高くなりすぎないように「MIN_INTERVAL」を下限に設定
# =====================================
# 初期化処理 # ★STEP5追加
# =====================================
def reset_game():
また、「reset_game」では以下のゲーム開始時にリセットしたい情報を一か所にまとめています。
- プレイヤーの位置・速度
- ジャンプ関連の状態
- 障害物リスト
- スコアやタイマー
- ゲーム開始時刻
これによって、「プログラム開始時」「ゲームオーバーからのリスタート時」に、同じ初期化処理を簡単に呼び出せるようになっています。
手順③:長押しジャンプの仕組み
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and not game_over:
if not is_jumping and not charging_jump:
charging_jump = True
jump_charge = 0
スペースキーを押した長さに応じてジャンプの高さが変わる仕組みを作ります。
スペースキーが押された瞬間に「charging_jump = True」とし、「ジャンプをため始めた」という状態にします。
同時に「jump_charge = 0」でため時間をリセットします。
if charging_jump:
jump_charge = min(jump_charge + dt, MAX_CHARGE_MS)
そして、「#更新処理」のループの中で上記を記載することで、1フレームごとに「dt(前フレームからの経過ミリ秒)」を足し込んでいき「現在どのくらい長押ししているか」を「jump_charge」にためていきます。
if event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE and charging_jump and not game_over:
ratio = min(jump_charge / MAX_CHARGE_MS, 1)
jump_power = JUMP_POWER_MIN + (JUMP_POWER_MAX - JUMP_POWER_MIN) * ratio
player_vy = jump_power
is_jumping = True
charging_jump = False
スペースキーを離した瞬間に、実際のジャンプ力を決めます。
- 「jump_charge / MAX_CHARGE_MS」
「どのくらいの割合でためたか(0〜1)」を計算
その割合に応じて、「JUMP_POWER_MIN」から「JUMP_POWER_MAX」の間でジャンプ力を補間します。 - 「player_vy」
ジャンプ力を代入し、「is_jumping = True」で空中状態にする
これで、「短押し=低いジャンプ」「長押し=高いジャンプ」実現されています。
手順④:時間経過で難易度アップとスコア加算
if not game_over:
passed_time = pygame.time.get_ticks() - start_ticks
obstacle_speed = calc_obstacle_speed(passed_time)
score += 1
更新処理の冒頭で記載しています。
- 「pygame.time.get_ticks()」
Pygame開始からの経過時間 - 「start_ticks」
はゲーム開始時点の時刻
その差を取ると「ゲーム開始からどれくらいたったか」が分かります。その値を「calc_obstacle_speed」「calc_spawn_interval」に渡すことで、時間に応じてスピードや出現頻度を変えています。
スコアについては、フレームごとに「score += 1」と少しずつ加算し、描画時に「score // 10」として10で割ってから表示することで、見た目の増え方を落ち着かせています。
手順⑤:ランダムな障害物とリスタート、画面表示
if obstacle_timer >= interval:
obstacle_timer = 0
w = random.randint(OBSTACLE_MIN_WIDTH, OBSTACLE_MAX_WIDTH)
h = random.randint(OBSTACLE_MIN_HEIGHT, OBSTACLE_MAX_HEIGHT)
x = WIDTH + random.randint(0, 100)
y = GROUND_Y - h
obstacles.append(pygame.Rect(x, y, w, h))
障害物の生成部分はこう変わりました。
- 幅と高さを「最小〜最大」の範囲からランダムに選択
- 出現位置を画面右の少し外側+0〜100ピクセルのランダムに選択
score_text = font.render(f"Score: {score // 10}", True, (255, 255, 255))
screen.blit(score_text, (10, 10))
info_text = font.render("Short press: low jump / Long press: high jump", True, (255, 255, 255))
screen.blit(info_text, (10, HEIGHT - 35))
if game_over:
msg1 = font.render("Game Over", True, (255, 255, 255))
msg2 = font.render("Press SPACE to Restart", True, (255, 255, 255))
screen.blit(msg1, (WIDTH // 2 - msg1.get_width() // 2, HEIGHT // 2 - 40))
screen.blit(msg2, (WIDTH // 2 - msg2.get_width() // 2, HEIGHT // 2))
最後に画面表示です。
- 上部にスコア
- 下部に「短押し/長押し」の英語説明
- ゲームオーバー時のみ中央にメッセージ
という構成になっています。
これで「横スクロールゲーム」が最後まで完成しました。
完成
以上でPythonで作る「横スクロールゲーム」の完成です。
ぜひ、コードをコピペするのではなく、実際にコードを打って作ってみてください。

お疲れさまでした。
