はじめに
「え?Amazon Q CLIってコードも書けるの?」
そんな半信半疑な気持ちで触ってみて、2Dゲームを作れるなんて思ってもいませんでした。
普段はAWSやアカウント管理や会社におけるクラウド活用検討といったインフラ寄りな仕事をしている私ですが、今回は巷で話題の生成AIの力を借りて「自分の手をほとんど動かさずにゲームを作る」という、夢のようなチャレンジに挑んでみました。
そのきっかけをくれたのは以下の記事です。皆さんも興味があれば読んでみてください。
そもそもAmazon Q CLIとは?
Amazon Q CLIは、コマンドラインで対話的にAIとやりとりができる新しい開発支援ツールです。
ターミナルから自然言語で質問すると、コード生成・修正・解説まで一気通貫でやってくれる頼れる相棒。
「こういう感じのジャンプアクションゲームを作りたいんだけど」
「Pygameを使って敵が追いかけてくる感じにして」
とAmazonQに問いかけるだけで、ゲームのロジックをいい感じに組んでくれます。
どんなゲームができたのか
完成したのは、シンプルな2Dジャンプアクションゲーム。
ざっくり特徴を挙げると、
- 青い棒人間が主人公(描画もQにおまかせ)
- ジャンプもできるし、左右に移動できる
- 敵が追いかけてくる
- 当たるとライフが減り、ゼロになるとゲームオーバー
- スコアも加算される
……と、「雑だけど、動くゲーム」です。
開発時間としては5分もないくらいですが、自分でゼロから作ってたら絶対途中で心が折れてました。
コーディング力ゼロでも作れる?
作れました。
正直、Pygameの書き方もPythonのクラスもよくわかっていません。
Amazon Q CLIに、「これができたら面白いかも」と伝えると、コードを返してくれます。
途中でバグが出ても、「このエラーなんとかして」って言えば直してくれます。
例えばこんな会話をしました。
- 「走るアニメーションつけたい」
→animate_run
関数を生成してくれた - 「ジャンプ中は腕を上げる感じにして」
→jump
関数内の描画ロジックを変更してくれた - 「ゲームオーバー画面ほしい」
→show_game_over_screen
関数をまるっと生成!
やってみて感じたこと
インフラエンジニアがコードを書くのって、結構ハードルが高いものです。
しかしAmazon Q CLIがあれば、
- コーディングのハードルをAIが勝手に下げてくれる
- やりたいことを自然言語で伝えればコードに変換してくれる
この体験は感動します。
今後は業務自動化にも活かしていきたい
今回は遊び感覚でゲームを作ってみましたが、「業務自動化」や「スクリプト化の推進」にも可能性があると感じました。
例えば、
- IAMポリシーの一括生成
- Configルールのテンプレート自動化
- PythonやShellでのAWS CLI連携スクリプト作成
- CDKなどのIaCコード改善支援
など、インフラ運用で発生する、ちょっとした手間や属人的な作業を自然言語ベースで整理していくことができるポテンシャルがあると感じました。
今後は、Amazon Q CLIを活用した日常業務の効率化や、社内の自動化推進にも積極的に取り組んでいきたいと思っています!
おわりに
「自分はインフラだから、アプリは無理」
そんな固定観念を打ち砕いてくれたのがAmazon Q CLIでした。
もし少しでも「作ってみたいけど無理」と思ってるなら、「Amazon Q CLIを使って、まずは雑でいいから作ってみる」をおすすめします!
弊社では一緒に働く仲間を募集中です!
現在、様々な職種を募集しております。
カジュアル面談も可能ですので、ご連絡お待ちしております!
募集内容等詳細は、是非採用サイトをご確認ください。
https://engineer.po-holdings.co.jp/
おまけ(実際のコード)
import pygame
import sys
import math
# Pygameの初期化
pygame.init()
# 画面設定
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("2D Platform Game")
# 色の定義
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
# 透明な背景のサーフェスを作成
self.image = pygame.Surface((40, 60), pygame.SRCALPHA)
self.facing_right = True
# 頭(より大きな円)
pygame.draw.circle(self.image, BLUE, (20, 15), 12)
# 目(白と黒)
if self.facing_right:
pygame.draw.circle(self.image, WHITE, (24, 13), 4) # 白目
pygame.draw.circle(self.image, BLACK, (25, 13), 2) # 黒目
else:
pygame.draw.circle(self.image, WHITE, (16, 13), 4) # 白目
pygame.draw.circle(self.image, BLACK, (15, 13), 2) # 黒目
# 胴体(より太い線)
pygame.draw.line(self.image, BLUE, (20, 27), (20, 45), 4)
# 腕(曲がった線)
# 左腕
pygame.draw.line(self.image, BLUE, (20, 32), (12, 40), 4)
pygame.draw.line(self.image, BLUE, (12, 40), (8, 35), 4)
# 右腕
pygame.draw.line(self.image, BLUE, (20, 32), (28, 40), 4)
pygame.draw.line(self.image, BLUE, (28, 40), (32, 35), 4)
# 脚(より自然な形)
# 左脚
pygame.draw.line(self.image, BLUE, (20, 45), (15, 55), 4)
pygame.draw.line(self.image, BLUE, (15, 55), (12, 58), 4)
# 右脚
pygame.draw.line(self.image, BLUE, (20, 45), (25, 55), 4)
pygame.draw.line(self.image, BLUE, (25, 55), (28, 58), 4)
self.rect = self.image.get_rect()
self.rect.x = 50
self.rect.y = WINDOW_HEIGHT - 100
self.velocity_y = 0
self.jumping = False
self.lives = 3
self.invincible = False
self.invincible_timer = 0
self.score = 0
self.original_image = self.image.copy()
self.run_frame = 0
self.animation_speed = 0.2
self.animation_time = 0
def update(self):
# 重力
self.velocity_y += 0.8
self.rect.y += self.velocity_y
if self.rect.bottom > WINDOW_HEIGHT - 50:
self.rect.bottom = WINDOW_HEIGHT - 50
self.velocity_y = 0
self.jumping = False
# アニメーション更新
self.animation_time += self.animation_speed
if self.animation_time >= 4:
self.animation_time = 0
# 無敵時の点滅
if self.invincible:
self.invincible_timer -= 1
if self.invincible_timer <= 0:
self.invincible = False
if self.invincible_timer % 4 < 2:
self.image.set_alpha(128)
else:
self.image.set_alpha(255)
def animate_run(self):
self.image = pygame.Surface((40, 60), pygame.SRCALPHA)
frame = int(self.animation_time)
# 頭と胴体は常に同じ
pygame.draw.circle(self.image, BLUE, (20, 15), 12) # 頭
pygame.draw.line(self.image, BLUE, (20, 27), (20, 45), 4) # 胴体
# 目
if self.facing_right:
pygame.draw.circle(self.image, WHITE, (24, 13), 4)
pygame.draw.circle(self.image, BLACK, (25, 13), 2)
else:
pygame.draw.circle(self.image, WHITE, (16, 13), 4)
pygame.draw.circle(self.image, BLACK, (15, 13), 2)
# 走るアニメーション
if frame < 2:
# 腕
pygame.draw.line(self.image, BLUE, (20, 32), (12, 40), 4)
pygame.draw.line(self.image, BLUE, (20, 32), (28, 40), 4)
# 脚
pygame.draw.line(self.image, BLUE, (20, 45), (15, 55), 4)
pygame.draw.line(self.image, BLUE, (20, 45), (25, 52), 4)
else:
# 腕
pygame.draw.line(self.image, BLUE, (20, 32), (15, 37), 4)
pygame.draw.line(self.image, BLUE, (20, 32), (25, 37), 4)
# 脚
pygame.draw.line(self.image, BLUE, (20, 45), (15, 52), 4)
pygame.draw.line(self.image, BLUE, (20, 45), (25, 55), 4)
if not self.facing_right:
self.image = pygame.transform.flip(self.image, True, False)
def move_left(self):
self.rect.x -= 5
self.facing_right = False
self.animate_run()
def move_right(self):
self.rect.x += 5
self.facing_right = True
self.animate_run()
def jump(self):
if not self.jumping:
self.velocity_y = -15
self.jumping = True
self.image = pygame.Surface((40, 60), pygame.SRCALPHA)
# ジャンプ時のポーズ
pygame.draw.circle(self.image, BLUE, (20, 15), 12) # 頭
pygame.draw.line(self.image, BLUE, (20, 27), (20, 45), 4) # 胴体
# 腕を上げる
pygame.draw.line(self.image, BLUE, (20, 32), (15, 25), 4)
pygame.draw.line(self.image, BLUE, (20, 32), (25, 25), 4)
# 脚を曲げる
pygame.draw.line(self.image, BLUE, (20, 45), (15, 50), 4)
pygame.draw.line(self.image, BLUE, (20, 45), (25, 50), 4)
if not self.facing_right:
self.image = pygame.transform.flip(self.image, True, False)
class Enemy(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface((30, 30))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.speed = 2
def update(self):
# プレイヤーの位置を取得して追いかける
player_pos = player.rect.center
enemy_pos = self.rect.center
# プレイヤーとの距離と方向を計算
dx = player_pos[0] - enemy_pos[0]
dy = player_pos[1] - enemy_pos[1]
distance = math.sqrt(dx**2 + dy**2)
# 距離が0でない場合のみ移動
if distance != 0:
# 正規化して移動
self.rect.x += (dx / distance) * self.speed
# 地面より下には行かない
if self.rect.bottom > WINDOW_HEIGHT - 50:
self.rect.bottom = WINDOW_HEIGHT - 50
def show_game_over_screen():
waiting = True
while waiting:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return False
if event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE:
return True
# ゲームオーバー画面の描画
screen.fill(BLACK) # 背景を黒に
# 大きなゲームオーバーテキスト
font_big = pygame.font.Font(None, 84)
game_over_text = font_big.render('GAME OVER', True, RED)
game_over_rect = game_over_text.get_rect(center=(WINDOW_WIDTH/2, WINDOW_HEIGHT/3))
# スコアの表示
font_medium = pygame.font.Font(None, 48)
score_text = font_medium.render(f'Final Score: {player.score}', True, WHITE)
score_rect = score_text.get_rect(center=(WINDOW_WIDTH/2, WINDOW_HEIGHT/2))
# 続行方法の説明
font_small = pygame.font.Font(None, 36)
continue_text = font_small.render('Press SPACE to Play Again', True, WHITE)
continue_rect = continue_text.get_rect(center=(WINDOW_WIDTH/2, WINDOW_HEIGHT * 2/3))
quit_text = font_small.render('Press X to Quit', True, WHITE)
quit_rect = quit_text.get_rect(center=(WINDOW_WIDTH/2, WINDOW_HEIGHT * 2/3 + 40))
# 画面に描画
screen.blit(game_over_text, game_over_rect)
screen.blit(score_text, score_rect)
screen.blit(continue_text, continue_rect)
screen.blit(quit_text, quit_rect)
pygame.display.flip()
clock.tick(60)
return False
def reset_game():
global player, enemies, all_sprites
# スプライトグループをリセット
all_sprites = pygame.sprite.Group()
enemies = pygame.sprite.Group()
# プレイヤーを初期化
player = Player()
all_sprites.add(player)
# 敵を再配置
for i in range(3):
enemy = Enemy(WINDOW_WIDTH - 100 * (i + 1), WINDOW_HEIGHT - 80)
all_sprites.add(enemy)
enemies.add(enemy)
# スプライトグループの作成
all_sprites = pygame.sprite.Group()
enemies = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
# 複数の敵を生成
for i in range(3):
enemy = Enemy(WINDOW_WIDTH - 100 * (i + 1), WINDOW_HEIGHT - 80)
all_sprites.add(enemy)
enemies.add(enemy)
# フォントの初期化
font = pygame.font.Font(None, 36)
# ゲームループ
clock = pygame.time.Clock()
game_active = True
running = True
while running:
if game_active:
# イベント処理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
player.jump()
# キー入力処理
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
player.move_left()
elif keys[pygame.K_RIGHT]:
player.move_right()
else:
# 静止時は基本ポーズに戻る
player.image = player.original_image.copy()
if not player.facing_right:
player.image = pygame.transform.flip(player.image, True, False)
# 衝突判定
if pygame.sprite.spritecollide(player, enemies, False) and not player.invincible:
player.lives -= 1
player.invincible = True
player.invincible_timer = 60
if player.lives <= 0:
game_active = False
else:
# プレイヤーを初期位置に戻す
player.rect.x = 50
player.rect.y = WINDOW_HEIGHT - 100
# 生存ボーナス
player.score += 10
# スプライトの更新
all_sprites.update()
# 描画
screen.fill(WHITE)
pygame.draw.rect(screen, GREEN, (0, WINDOW_HEIGHT - 50, WINDOW_WIDTH, 50))
all_sprites.draw(screen)
# スコアとライフの表示
score_text = font.render(f'Score: {player.score}', True, BLACK)
lives_text = font.render(f'Lives: {player.lives}', True, BLACK)
screen.blit(score_text, (10, 10))
screen.blit(lives_text, (10, 40))
pygame.display.flip()
else:
# ゲームオーバー画面を表示
if show_game_over_screen():
# リスタート
reset_game()
game_active = True
else:
running = False
clock.tick(60)
pygame.quit()
sys.exit()