🎮
Amazon Q Developer CLIとPygameを使って簡単な横スクロールアクションゲームを作ってみた
今回はAmazon Q Developer CLIとPygameを使って簡単な横スクロールアクションゲームを作ってみたいと思います。
はじめに
Amazon Q Developerについて
Amazon Q Developerは、生成AIを活用した会話アシスタントです。
特に、Amazon Q Developer CLIはChatGPTのCLI版みたいなものだと思って頂ければイメージしやすいかも知れません。
Amazon Q Developer CLIは今のところ(2025/05時点)無料で利用できます。
Pygameについて
Pygameはその名の通りゲームを製作するために設計されたクロスプラットフォームのPythonモジュールになっています。
公式サイトに作例がありますが、かなり本格的なゲームも作ることができます。
Amazon Q Developer CLIのインストール
OSによってインストール方法が異なりますが、Macの場合はHomebrewで簡単にインストールすることができます。
$ brew install amazon-q
その他のOSについては以下公式ドキュメントをご確認下さい。
Pygamesのインストール
pip
を利用して、以下でインストール可能です。
$ pip install pygame
作ってみた
表題の通り、Amazon Q Developer CLIに簡単な横スクロールアクションゲームを作ってもらいました。
プレイ映像は以下の通りです。
ランダムに生成される地形を自転車(見た目はほぼトロッコですが...笑)で走りながら、道中のコインを取得しつつ距離を稼ぐゲームです。
上記は私の好きなチャリ走というゲームにインスピレーションを受け作成をしております。
生成AIを利用し極短時間で作成したものになるのでまだまだバグ等もあるかと思いますが、コードは以下の通りです。
Pythonコード
main.py
import pygame
import random
# Pygameの初期化
pygame.init()
# 定数
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
GRAY = (128, 128, 128)
DARK_GRAY = (64, 64, 64)
class Player:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 40
self.height = 30
self.vel_y = 0
self.vel_x = 0
self.on_ground = False
self.speed = 5
self.jump_power = -15
self.gravity = 0.8
self.ground_y = SCREEN_HEIGHT - 100
self.can_jump_timer = 0 # ジャンプ可能時間
self.was_in_hole = False # 前フレームで穴にいたか
self.falling_from_hole = False # 穴から落下中かどうか
def update(self, terrain):
# 重力の適用
self.vel_y += self.gravity
# Y座標の更新
old_y = self.y
self.y += self.vel_y
# プレイヤーの足元の複数点で地面をチェック
left_foot_x = self.x + 5
right_foot_x = self.x + self.width - 5
center_foot_x = self.x + self.width // 2
# 複数点での地面の高さを取得
left_ground = terrain.get_ground_height(left_foot_x)
right_ground = terrain.get_ground_height(right_foot_x)
center_ground = terrain.get_ground_height(center_foot_x)
# 地面がある場所の最高点を取得
ground_heights = [h for h in [left_ground, right_ground, center_ground] if h is not None]
# 現在穴の上にいるかチェック
currently_in_hole = len(ground_heights) == 0
# 穴から落下状態の管理
if self.was_in_hole and currently_in_hole and self.vel_y > 0:
self.falling_from_hole = True
if ground_heights:
# 地面がある場合
highest_ground = min(ground_heights) # Y座標なので小さい値が高い
# 壁衝突判定:穴から落下中に地面に横から衝突した場合のみ
if (self.falling_from_hole and self.vel_y > 0 and
self.y + self.height > highest_ground):
# プレイヤーの中心が地面より下にある場合は壁衝突
player_center_y = self.y + self.height // 2
if player_center_y > highest_ground:
return True # ゲームオーバー(壁に衝突)
# 正常な着地判定
if self.y + self.height >= highest_ground and self.vel_y >= 0:
self.y = highest_ground - self.height
self.vel_y = 0
self.on_ground = True
self.can_jump_timer = 10 # 10フレーム間はジャンプ可能
self.falling_from_hole = False # 着地したので穴からの落下状態をリセット
else:
self.on_ground = False
else:
# 完全に穴の上にいる場合
self.on_ground = False
# 穴の状態を更新
self.was_in_hole = currently_in_hole
# ジャンプ可能時間の減少
if self.can_jump_timer > 0:
self.can_jump_timer -= 1
# 画面下に落ちたらゲームオーバー
if self.y > SCREEN_HEIGHT:
return True # ゲームオーバー
return False # ゲーム続行
def jump(self):
# 地面にいるか、最近まで地面にいた場合ジャンプ可能
if self.on_ground or self.can_jump_timer > 0:
self.vel_y = self.jump_power
self.on_ground = False
self.can_jump_timer = 0
self.falling_from_hole = False # ジャンプ時は穴からの落下状態をリセット
def draw(self, screen, camera_x):
draw_x = self.x - camera_x
# 自転車のシンプルな描画
# 車体
pygame.draw.rect(screen, BLUE, (draw_x, self.y, self.width, self.height))
# タイヤ
pygame.draw.circle(screen, BLACK, (int(draw_x + 8), int(self.y + self.height)), 8)
pygame.draw.circle(screen, BLACK, (int(draw_x + self.width - 8), int(self.y + self.height)), 8)
class TerrainPoint:
def __init__(self, x, y, is_hole=False):
self.x = x
self.y = y
self.is_hole = is_hole
class Terrain:
def __init__(self):
self.points = []
self.base_height = SCREEN_HEIGHT - 100
self.min_height = 150 # 地面の最高位置(画面上端から150ピクセル下)
self.max_height = SCREEN_HEIGHT - 50 # 地面の最低位置
self.segment_width = 40 # 各地形セグメントの幅
self.generate_initial_terrain()
def generate_initial_terrain(self):
# 初期地形の生成
current_x = 0
current_height = self.base_height
# 最初は平坦な地面から開始
for i in range(20):
self.points.append(TerrainPoint(current_x, current_height))
current_x += self.segment_width
# 地形を先まで生成
self.extend_terrain(current_x, current_height)
def extend_terrain(self, start_x, start_height):
current_x = start_x
current_height = start_height
while current_x < start_x + 2000: # 2000ピクセル分生成
# 次の地形タイプを決定
terrain_type = random.choices(
['flat', 'up', 'down', 'hole'],
weights=[40, 25, 25, 10]
)[0]
if terrain_type == 'flat':
# 平坦
length = random.randint(3, 8)
for i in range(length):
self.points.append(TerrainPoint(current_x, current_height))
current_x += self.segment_width
elif terrain_type == 'up':
# 上り坂
length = random.randint(4, 7)
max_possible_change = current_height - self.min_height
height_change = min(random.randint(40, 100), max_possible_change)
if height_change > 0:
height_per_step = height_change // length
for i in range(length):
y = max(self.min_height, current_height - height_per_step * (i + 1))
self.points.append(TerrainPoint(current_x, y))
current_x += self.segment_width
current_height = max(self.min_height, current_height - height_change)
else:
# 上がれない場合は平坦にする
for i in range(length):
self.points.append(TerrainPoint(current_x, current_height))
current_x += self.segment_width
elif terrain_type == 'down':
# 下り坂
length = random.randint(4, 7)
max_possible_change = self.max_height - current_height
height_change = min(random.randint(40, 100), max_possible_change)
if height_change > 0:
height_per_step = height_change // length
for i in range(length):
y = min(self.max_height, current_height + height_per_step * (i + 1))
self.points.append(TerrainPoint(current_x, y))
current_x += self.segment_width
current_height = min(self.max_height, current_height + height_change)
else:
# 下がれない場合は平坦にする
for i in range(length):
self.points.append(TerrainPoint(current_x, current_height))
current_x += self.segment_width
elif terrain_type == 'hole':
# 穴(ジャンプで越えられるサイズに制限)
# プレイヤーのジャンプ距離を考慮(速度5 × 約30フレーム = 150ピクセル程度)
hole_width = random.randint(2, 3) # 2-3セグメント(80-120ピクセル)
for i in range(hole_width):
self.points.append(TerrainPoint(current_x, current_height, is_hole=True))
current_x += self.segment_width
def get_ground_height(self, x):
# 指定されたX座標での地面の高さを取得
if not self.points:
return self.base_height
# X座標に対応する地形ポイントを見つける
for i, point in enumerate(self.points):
if point.x <= x < point.x + self.segment_width:
if point.is_hole:
return None # 穴の場合
else:
return point.y
# 見つからない場合は最後のポイントの高さ
if self.points:
last_point = self.points[-1]
return last_point.y if not last_point.is_hole else self.base_height
return self.base_height
def update(self, camera_x):
# カメラの位置に基づいて新しい地形を生成
rightmost_x = max(point.x for point in self.points) if self.points else 0
if camera_x + SCREEN_WIDTH > rightmost_x - 1000:
# 新しい地形を生成
last_solid_point = None
for point in reversed(self.points):
if not point.is_hole:
last_solid_point = point
break
if last_solid_point:
self.extend_terrain(rightmost_x, last_solid_point.y)
else:
self.extend_terrain(rightmost_x, self.base_height)
def draw(self, screen, camera_x):
# 地形の描画(線は描画しない)
visible_points = [p for p in self.points if p.x - camera_x > -100 and p.x - camera_x < SCREEN_WIDTH + 100]
# 地面の塗りつぶしのみ
for point in visible_points:
draw_x = point.x - camera_x
if not point.is_hole:
pygame.draw.rect(screen, GRAY,
(draw_x, point.y, self.segment_width, SCREEN_HEIGHT - point.y))
class Coin:
def __init__(self, x, y):
self.x = x
self.y = y
self.radius = 10
self.collected = False
self.spawn_x = x # 生成時のX座標を記録
def draw(self, screen, camera_x):
if not self.collected:
draw_x = self.x - camera_x
# 画面右端から徐々に現れるエフェクト
if draw_x > SCREEN_WIDTH - 50:
# 透明度を調整(画面端では薄く)
alpha = max(0, min(255, (SCREEN_WIDTH - draw_x) * 5))
if alpha > 50: # 十分に見える場合のみ描画
pygame.draw.circle(screen, YELLOW, (int(draw_x), int(self.y)), self.radius)
elif -20 <= draw_x <= SCREEN_WIDTH:
pygame.draw.circle(screen, YELLOW, (int(draw_x), int(self.y)), self.radius)
class Game:
def __init__(self):
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Bicycle Runner")
self.clock = pygame.time.Clock()
self.player = Player(100, SCREEN_HEIGHT - 150)
self.terrain = Terrain()
self.coins = []
self.camera_x = 0
self.score = 0
self.distance = 0
self.game_over = False
self.font = pygame.font.Font(None, 36)
self.coin_spawn_timer = 0
def generate_coins(self):
# コインの動的生成(画面右端の先に配置)
self.coin_spawn_timer += 1
if self.coin_spawn_timer > 180: # 3秒ごと
# 画面右端より先にコインを配置
x = self.camera_x + SCREEN_WIDTH + random.randint(100, 300)
ground_height = self.terrain.get_ground_height(x)
if ground_height is not None: # 地面がある場所にのみ配置
y = ground_height - random.randint(40, 100)
self.coins.append(Coin(x, y))
self.coin_spawn_timer = 0
# 古いコインを削除(プレイヤーから遠く離れたもの)
self.coins = [coin for coin in self.coins if coin.x > self.camera_x - 200]
def check_collisions(self):
player_rect = pygame.Rect(self.player.x, self.player.y,
self.player.width, self.player.height)
# コインとの衝突
for coin in self.coins:
if not coin.collected:
coin_rect = pygame.Rect(coin.x - coin.radius, coin.y - coin.radius,
coin.radius * 2, coin.radius * 2)
if player_rect.colliderect(coin_rect):
coin.collected = True
self.score += 10
def update(self):
if not self.game_over:
# プレイヤーの更新
game_over = self.player.update(self.terrain)
if game_over:
self.game_over = True
# プレイヤーを右に移動
self.player.x += self.player.speed
# カメラの更新(プレイヤーを追従)
self.camera_x = self.player.x - 200
# 地形の更新
self.terrain.update(self.camera_x)
# コインの生成と管理
self.generate_coins()
self.check_collisions()
self.distance = int(self.player.x / 10)
def draw_ui(self):
# スコア表示
score_text = self.font.render(f"Score: {self.score}", True, BLACK)
self.screen.blit(score_text, (10, 10))
# 距離表示
distance_text = self.font.render(f"Distance: {self.distance}m", True, BLACK)
self.screen.blit(distance_text, (10, 50))
# 操作説明
if self.distance < 50: # 最初の5秒間だけ表示
help_text = pygame.font.Font(None, 24).render("Press SPACE to jump!", True, BLACK)
self.screen.blit(help_text, (10, 90))
if self.game_over:
game_over_text = self.font.render("GAME OVER - Press R to Restart", True, RED)
text_rect = game_over_text.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2))
self.screen.blit(game_over_text, text_rect)
def draw(self):
self.screen.fill((135, 206, 235)) # 空の色
# 地形の描画
self.terrain.draw(self.screen, self.camera_x)
# プレイヤーの描画
self.player.draw(self.screen, self.camera_x)
# コインの描画
for coin in self.coins:
coin.draw(self.screen, self.camera_x)
self.draw_ui()
pygame.display.flip()
def restart(self):
self.player = Player(100, SCREEN_HEIGHT - 150)
self.terrain = Terrain()
self.coins = []
self.camera_x = 0
self.score = 0
self.distance = 0
self.game_over = False
self.coin_spawn_timer = 0
def run(self):
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if not self.game_over:
self.player.jump()
elif event.key == pygame.K_r and self.game_over:
self.restart()
self.update()
self.draw()
self.clock.tick(60)
pygame.quit()
if __name__ == "__main__":
game = Game()
game.run()
最後に
このように、Amazon Q DeveloperとPygameを利用すれば簡単にゲームが作れますので、皆さんも一度試してみて頂ければと思います。
参考になりましたら幸いです。
Discussion