先日、AICUで開催された「AICU Creators Talk 5 Pythonをゲーム開発で楽しく学ぶ 書籍出版記念ワークショップ」に参加してきました。このイベントでは、Pyxelの開発者である北尾崇さん(@kitao)が「ゲーム開発少年からの30年を30分で!」というタイトルで講演されていました。
https://note.com/aicu/n/ne9cdfd210a33
実は私、その講演を聴きながら「じゃあ私も30分でゲーム作れるんじゃね?」と思い立ち、その場でシューティングゲームを開発してみることにしました。今回はその開発過程と成果を紹介します!
Pyxelとは?
Pyxelは北尾さんが開発したPython向けのレトロゲームエンジンです。16色のカラーパレットや同時再生音数の制限など、あえてレトロな制約を設けることで、初心者でも取り組みやすく、また創造性を引き出すことを目的としています。
https://github.com/kitao/pyxel/blob/main/docs/README.ja.md
Pyxelは、開発者の北尾氏により、Pyxelを使ったゲーム開発方法をまとめた書籍として販売されています。
私自身、Pythonは使ったことがあるものの、Pyxelは今回が初めての挑戦。「30分でゲームが作れるのか?」という疑問を抱きながらも、思い切って挑戦することにしました。
開発環境の準備
今回は以下のような環境で開発を行いました:
- 
エディタ:Windsurf
 - 
開発支援:Claude 3.5 Sonnet(AI)
 - 
参考資料:PyxelのREADMEとサンプルコード
 
プロジェクトのファイル構成はこんな感じです。
pyxel-test/  
├── docs/ - プロジェクトのドキュメントが含まれるディレクトリ  
│   ├── pyxel-manual.md - Pyxel のREADMEとサンプルコードをまとめたファイル
│   ├── ゲーム仕様書.md - ゲームの仕様書(日本語)  
├── assets.pyxres - Pyxelのリソースファイル(画像、タイルマップ、サウンドなど)  
├── main.py - プロジェクトのメインのPythonスクリプトファイル  
└── README.md - プロジェクトの概要や使用方法を記載したドキュメント  
Pyxel Editorでリソース作成
ゲーム開発の最初のステップとして、Pyxel Editorを使ってゲームで使用する画像リソースを作成しました。コマンドラインで以下のコマンドを実行すると、Pyxel Editorが起動します。
pyxel edit assets.pyxres
Pyxel Editorには以下の4つの編集モードがあります。
- 
Image Editor: 各イメージバンクの画像を編集するモード
 - 
Tilemap Editor: イメージバンクの画像をタイルパターンで配置するモード
 - 
Sound Editor: メロディやサウンドエフェクトに使用するサウンドを編集するモード
 - 
Music Editor: サウンドを再生順に配置した音楽を編集するモード
 
今回は主にImage Editorを使用して、プレイヤー、敵、ボス、弾などのスプライトを作成しました。ドット絵を描くのは初めてでしたが、Pyxel Editorのシンプルなインターフェースのおかげで直感的に操作できました。
作成したスプライトは以下の通りです。
- 
プレイヤーの宇宙船 (8x8ピクセル)
 
- 
敵の小型機 (8x8ピクセル)
 
- 
中ボス (16x16ピクセル)
 
- 
ラスボス (16x16ピクセル)
 
- 
弾 (8x2ピクセル)
 
リソースファイルが完成したら、「pyxel.load("assets.pyxres")」でゲーム内に読み込むことができます。
AIの力を借りた開発プロセス
実は今回、開発をスピードアップするためにClaude 3.5 Sonnetの力を借りました。具体的には、PyxelのREADMEとサンプルコードをAIに読み込ませ、シューティングゲームの基本構造を提案してもらいました。
AIとの対話例
私:「Pyxelを使って簡単なシューティングゲームを作りたいです。プレイヤーが移動して弾を撃ち、敵を倒すゲームの基本構造を教えてください。」
Claude:「Pyxelでシューティングゲームを作るなら、まずはゲームのメインループを作成し、そこにプレイヤーの移動、弾の発射、敵の生成と移動、当たり判定などの機能を実装していきます。以下に基本構造を示します...」
こうしたやり取りを通じて、徐々にゲームの骨格を作り上げていきました。最終的に出力されたゲームの仕様は以下の通りです。
# シューティングゲーム仕様書
## 1. ゲーム概要
横スクロールシューティングゲームで、プレイヤーは宇宙船を操作して敵を倒しながら、中ボスとラスボスに挑戦します。
### 1.1 ゲームの目的
- 敵を倒してスコアを獲得
- 中ボス(スコア10)とラスボス(スコア30)を倒す
- ラスボスを倒すとゲームクリア
## 2. ゲームシステム
### 2.1 基本操作
- 矢印キー: 上下左右移動
- スペースキー: 弾を発射
### 2.2 プレイヤーキャラクター
- スプライト: assets.pyxres (0, 0)から8x8の領域
- 3機のライフ
- 敵や弾に当たるとライフが減少
- 被弾後は一時的な無敵時間あり(点滅で表示)
### 2.3 弾システム
- スプライト: assets.pyxres (8, 0)から8x2の領域
- スペースキー長押しで連射可能(10フレームごとに発射)
- 直線的に右方向へ移動
- 画面外に出ると消滅
### 2.4 通常の敵
- スプライト: assets.pyxres (16, 0)から8x8の領域
- 画面右から左へ移動
- ランダムな速度(1.0-2.0の範囲)
- 30フレームごとにランダムな高さから出現
- 弾に当たると消滅し、スコア+1
## 3. ボスキャラクター
### 3.1 中ボス(スコア10で出現)
- スプライト: assets.pyxres (40, 0)から16x16の領域
- HP: 20
- 倒すとスコア+10
- 移動パターン:
  - 画面右側で上下に揺れる
  - 左右にも小さく移動
- 攻撃パターン:
  - 3方向に弾を発射
  - 30フレームごとに攻撃
### 3.2 ラスボス(スコア30で出現)
- スプライト: assets.pyxres (24, 0)から16x16の領域
- HP: 40
- 倒すとスコア+20とゲームクリア
- 4つのフェーズを180フレームごとに切り替え
- 移動パターン:
  1. 8の字移動(振幅20px)
  2. ジグザグ移動(振幅15px)
  3. 円運動(半径15px)
  4. 急速な直線移動(振幅10px)
- 攻撃パターン:
  1. 扇状の弾幕(8方向)
  2. プレイヤー追尾弾(3方向)
  3. 螺旋状の弾幕(4方向)
  4. 全方向ランダム弾(6発)
## 4. ゲームの状態
### 4.1 ゲームオーバー条件
- プレイヤーのライフが0になる
- 「GAME OVER」表示
- Rキーでリスタート可能
### 4.2 ゲームクリア条件
- ラスボスを倒す
- 「GAME CLEAR!」表示
- Rキーでリスタート可能
### 4.3 画面表示
- ゲーム画面サイズ: 160x120ピクセル
- スコア表示: 左上
- 残機表示: スコアの下
## 5. エフェクト
### 5.1 爆発エフェクト
- キャラクターが破壊されると発生
- パーティクルが四方八方に飛び散る
- パーティクルの色: 8-10番の色を使用
- パーティクルの寿命: 30フレーム
### 5.2 その他のエフェクト
- 無敵時間中のプレイヤー点滅
- ボスのHPバー表示
- ボスの弾は通常の弾より大きく表示
## 6. 技術仕様
- 開発言語: Python
- 使用ライブラリ: Pyxel
- アセット管理: assets.pyxres
- フレームレート: 30FPS(Pyxelのデフォルト)
ゲームの実装
シューティングゲームの基本的な要素は以下の通りです:
- 
プレイヤーの移動と弾の発射
 - 
敵の生成と移動
 - 
当たり判定の実装
 - 
ボス戦の実装
 - 
ゲームオーバー/クリア条件の設定
 
プレイヤーの実装
プレイヤーは矢印キーで上下左右に移動でき、スペースキーで弾を発射します。連射も可能で、ホールド中は一定間隔で弾が出ます。
# プレイヤーの移動
if pyxel.btn(pyxel.KEY_UP):
    self.player_y = max(self.player_y - self.player_speed, 0)
if pyxel.btn(pyxel.KEY_DOWN):
    self.player_y = min(self.player_y + self.player_speed, pyxel.height - 8)
if pyxel.btn(pyxel.KEY_LEFT):
    self.player_x = max(self.player_x - self.player_speed, 0)
if pyxel.btn(pyxel.KEY_RIGHT):
    self.player_x = min(self.player_x + self.player_speed, pyxel.width - 8)
# 弾の発射(連射対応)
if pyxel.btn(pyxel.KEY_SPACE):
    self.shoot_timer += 1
    if self.shoot_timer == 1 or self.shoot_timer % 5 == 0:  # 初回か5フレームごとに発射
        self.bullets.append({
            "x": self.player_x + 8,
            "y": self.player_y + 3,
            "speed": 4
        })
else:
    self.shoot_timer = 0  # スペースキーを離したらタイマーをリセット
敵の実装
敵は画面右から一定間隔で出現し、左に向かって移動します。ボスは特定のスコアに達すると出現します。
# 敵の生成
self.enemy_timer += 1
if self.enemy_timer >= 30 and not self.boss:
    self.enemies.append({
        "x": pyxel.width,
        "y": pyxel.rndi(0, pyxel.height - 8),
        "speed": pyxel.rndf(1, 2)
    })
    self.enemy_timer = 0
ボス戦の実装
ゲームには中ボスとラスボスの2種類のボスが登場します。それぞれ異なる移動パターンと攻撃パターンを持っています。
def spawn_boss(self, boss_type):
    if boss_type == "mid":
        self.boss = {
            "type": "mid",
            "x": pyxel.width - 40,
            "y": pyxel.height // 2 - 8,
            "width": 16,
            "height": 16,
            "hp": 20,
            "score": 10,
            # 他のパラメータ...
        }
    else:  # final boss
        self.boss = {
            "type": "final",
            "x": pyxel.width - 48,
            "y": pyxel.height // 2 - 8,
            "width": 16,
            "height": 16,
            "hp": 40,
            "score": 20,
            # 他のパラメータ...
        }
開発中に直面した課題
当たり判定の実装
当たり判定は単純な矩形同士の衝突検出で実装しましたが、プレイヤーが死亡した後の処理や無敵時間の実装に少し手間取りました。
# プレイヤーと敵の当たり判定
for enemy in self.enemies[:]:
    if (self.player_x < enemy["x"] + 8 and
        self.player_x + 8 > enemy["x"] and
        self.player_y < enemy["y"] + 8 and
        self.player_y + 8 > enemy["y"]):
        self.create_explosion(self.player_x + 4, self.player_y + 4)
        self.is_alive = False
        self.explosion_timer = 30
        self.lives -= 1
        if self.lives <= 0:
            self.game_over = True
        break
ボスの攻撃パターン設計
特にラスボスの複雑な攻撃パターンの実装には時間がかかりました。4つのフェーズを作り、それぞれ異なる弾幕パターンを実装しています。
# ラスボスの攻撃(フェーズに応じて変化)
if boss["attack_phase"] == 0:
    # 扇状の弾幕
    for i in range(8):
        angle = i * 0.785 + pyxel.sin(boss["move_timer"] * 0.1)
        self.boss_bullets.append({
            "x": boss["x"] + boss["width"]/2,
            "y": boss["y"] + boss["height"]/2,
            "dx": -4 * pyxel.cos(angle),
            "dy": 4 * pyxel.sin(angle)
        })
完成したゲームの特徴
完成したゲームは以下になります。プレイ映像の一部になりますが、ボス戦までの様子をご覧いただけます(動画は4倍速になります)。
細かく画面を確認していきます。左に自機がおり、右から左に向かって敵キャラが向かってきます。左上にはスコアと自機数が表示され、3回やられるとゲームオーバーです。敵キャラを倒すと、火花が散るようにしています。
以下は中ボスとの戦闘シーンです。ボスキャラの上部には緑色の体力バーが表示されており、これを0にすると撃破です。
以下は大ボスです。大ボスを撃破するとゲームクリアです。
大ボスを撃破し、無事ゲームクリアになります。
30分という短時間で開発したにも関わらず、以下のような機能を実装することができました。
- 
プレイヤーの移動と連射機能
 - 
敵の自動生成と移動
 - 
2種類のボスバトル(中ボスとラスボス)
 - 
複数の攻撃パターンと移動パターン
 - 
爆発エフェクト
 - 
スコア表示とライフシステム
 - 
ゲームオーバー/クリア画面
 
最終的なコードは以下になります。
★本稿はアイキューマガジンVol.10に収録予定です、お楽しみに!
—
この記事の続きはこちらから https://note.com/aicu/n/n4ed8d9da3f80
Originally published at https://note.com on Mar 5, 2025.
      
    
Comments