Pyxelで楽しむレトロゲーム開発：初心者から上級者まで学ぶ15章

## はじめに

Pyxelは、レトロゲーム開発を楽しむためのPython向けゲームエンジンです。16色のカラーパレット、256x256ピクセルの画面サイズ、4チャンネルの音声など、懐かしいレトロゲーム機の雰囲気を再現しています。Pythonプログラミングの知識があれば、誰でも簡単にゲーム開発を始められるのが魅力です。この記事では、Pyxelの基本から応用まで、15章に渡って詳しく解説していきます。

## 第1章: Pyxelのインストールと初期設定

Pyxelを使い始めるには、まずPythonをインストールする必要があります。Pythonがインストールされたら、コマンドプロンプトやターミナルで以下のコマンドを実行してPyxelをインストールします。

```python
pip install pyxel
```

インストールが完了したら、以下のコードを書いて、Pyxelが正しく動作するか確認しましょう。このコードは、160x120ピクセルの黒い画面を表示します。

```python
import pyxel

pyxel.init(160, 120, title="Pyxel Test")

def update():
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)

pyxel.run(update, draw)
```

このコードを実行すると、黒い画面が表示され、Qキーを押すと終了します。これでPyxelの基本的な設定は完了です。

## 第2章: 図形の描画

Pyxelでは、様々な基本図形を簡単に描画できます。ここでは、線、矩形、円を描画する方法を学びます。

```python
import pyxel

pyxel.init(160, 120, title="図形描画")

def update():
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    
    # 線を描画
    pyxel.line(10, 10, 60, 60, 7)
    
    # 矩形を描画
    pyxel.rect(70, 10, 40, 40, 8)
    
    # 円を描画
    pyxel.circ(40, 80, 20, 12)

pyxel.run(update, draw)
```

このコードでは、白い線、緑の矩形、赤い円を描画しています。pyxel.line()、pyxel.rect()、pyxel.circ()関数を使用して、それぞれの図形を描画しています。最後の引数は色を指定しており、Pyxelの16色パレットの中から選択します。

## 第3章: キー入力の処理

ゲームには入力処理が不可欠です。Pyxelでは、キーボードやマウスの入力を簡単に処理できます。ここでは、キーボード入力を使って四角形を動かす例を示します。

```python
import pyxel

pyxel.init(160, 120, title="キー入力")

x = 80
y = 60

def update():
    global x, y
    
    if pyxel.btn(pyxel.KEY_LEFT):
        x = max(x - 2, 0)
    if pyxel.btn(pyxel.KEY_RIGHT):
        x = min(x + 2, 160)
    if pyxel.btn(pyxel.KEY_UP):
        y = max(y - 2, 0)
    if pyxel.btn(pyxel.KEY_DOWN):
        y = min(y + 2, 120)
    
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    pyxel.rect(x, y, 10, 10, 11)

pyxel.run(update, draw)
```

このコードでは、矢印キーで四角形を動かすことができます。pyxel.btn()関数でキーが押されているかを判定し、x座標とy座標を更新しています。画面外に出ないよう、max()とmin()関数で座標を制限しています。

## 第4章: 音楽と効果音

Pyxelでは、レトロゲーム風の音楽や効果音を簡単に作成・再生できます。ここでは、簡単な効果音を鳴らす例を示します。

```python
import pyxel

pyxel.init(160, 120, title="音楽と効果音")

# 効果音の定義
pyxel.sound(0).set(
    note="C3E3G3C4", tone="T", volume="7", effect="N", speed=20
)

def update():
    if pyxel.btnp(pyxel.KEY_SPACE):
        pyxel.play(0, 0)
    
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    pyxel.text(10, 10, "Press SPACE to play sound", 7)

pyxel.run(update, draw)
```

このコードでは、スペースキーを押すと効果音が鳴ります。pyxel.sound()で効果音を定義し、pyxel.play()で再生しています。音符、音色、音量、エフェクト、速度を指定することで、様々な効果音を作成できます。

## 第5章: スプライトの使用

スプライトは、ゲーム内のキャラクターや物体を表現するのに使用される小さな画像です。Pyxelでは、イメージバンクを使ってスプライトを管理します。

```python
import pyxel

pyxel.init(160, 120, title="スプライト")

# スプライトの作成
pyxel.image(0).load(0, 0, "assets/player.png")

player_x = 80
player_y = 60

def update():
    global player_x, player_y
    
    if pyxel.btn(pyxel.KEY_LEFT):
        player_x = max(player_x - 2, 0)
    if pyxel.btn(pyxel.KEY_RIGHT):
        player_x = min(player_x + 2, 152)
    
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    pyxel.blt(player_x, player_y, 0, 0, 0, 8, 8, 0)

pyxel.run(update, draw)
```

このコードでは、外部のPNG画像をスプライトとして読み込み、画面上に表示しています。pyxel.image().load()で画像を読み込み、pyxel.blt()で画面に描画します。左右の矢印キーでスプライトを動かすことができます。

## 第6章: コリジョン検出

ゲームでは、オブジェクト同士の衝突判定（コリジョン検出）が重要です。ここでは、プレイヤーと敵の衝突を検出する簡単な例を示します。

```python
import pyxel

pyxel.init(160, 120, title="コリジョン検出")

player_x = 80
player_y = 100
enemy_x = 80
enemy_y = 20

def update():
    global player_x, player_y, enemy_x, enemy_y
    
    if pyxel.btn(pyxel.KEY_LEFT):
        player_x = max(player_x - 2, 0)
    if pyxel.btn(pyxel.KEY_RIGHT):
        player_x = min(player_x + 2, 152)
    
    # 衝突判定
    if (abs(player_x - enemy_x) < 8 and abs(player_y - enemy_y) < 8):
        pyxel.play(0, 0)
    
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    pyxel.rect(player_x, player_y, 8, 8, 11)
    pyxel.rect(enemy_x, enemy_y, 8, 8, 8)

pyxel.sound(0).set(note="C3", tone="N", volume="7", effect="N", speed=3)

pyxel.run(update, draw)
```

このコードでは、プレイヤーと敵の位置を比較し、両者の距離が一定以下になったら衝突したと判定します。衝突時には効果音を鳴らします。abs()関数を使って、x座標とy座標の差の絶対値を計算しています。

## 第7章: アニメーション

アニメーションは、ゲームに動きと生命感を与えます。Pyxelでは、フレームごとに異なる画像を表示することで、簡単にアニメーションを作成できます。

```python
import pyxel

pyxel.init(160, 120, title="アニメーション")

# アニメーションフレームの作成
pyxel.image(0).load(0, 0, "assets/character_walk.png")

character_x = 80
character_y = 60
frame = 0

def update():
    global character_x, frame
    
    if pyxel.btn(pyxel.KEY_RIGHT):
        character_x = min(character_x + 2, 152)
        frame = (frame + 1) % 4
    elif pyxel.btn(pyxel.KEY_LEFT):
        character_x = max(character_x - 2, 0)
        frame = (frame + 1) % 4
    else:
        frame = 0
    
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    pyxel.blt(character_x, character_y, 0, frame * 8, 0, 8, 8, 0)

pyxel.run(update, draw)
```

このコードでは、4フレームのウォークアニメーションを実装しています。キャラクターが移動している間、frameカウンターを更新し、それに応じて異なるスプライトを表示します。pyxel.blt()関数の引数を変更することで、表示するスプライトを切り替えています。

## 第8章: パーティクルシステム

パーティクルシステムは、火花、煙、雨などの視覚効果を作成するのに使用されます。ここでは、簡単な雪のパーティクルシステムを実装します。

```python
import pyxel
import random

pyxel.init(160, 120, title="パーティクルシステム")

class Snowflake:
    def __init__(self):
        self.x = random.randint(0, 160)
        self.y = random.randint(-10, 0)
        self.speed = random.uniform(0.5, 1.5)

    def update(self):
        self.y += self.speed
        if self.y > 120:
            self.y = random.randint(-10, 0)
            self.x = random.randint(0, 160)

snowflakes = [Snowflake() for _ in range(50)]

def update():
    for snowflake in snowflakes:
        snowflake.update()
    
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(1)
    for snowflake in snowflakes:
        pyxel.pset(snowflake.x, snowflake.y, 7)

pyxel.run(update, draw)
```

このコードでは、50個の雪片を生成し、それぞれを独立して動かしています。各雪片は画面の上から下に落ち、画面の下に達すると再び上から落ち始めます。pyxel.pset()関数を使って、各雪片を1ピクセルの点として描画しています。

## 第9章: タイルマップの使用

タイルマップは、大きなゲーム世界を効率的に作成するのに役立ちます。Pyxelでは、8つのタイルマップを使用できます。

```python
import pyxel

pyxel.init(160, 120, title="タイルマップ")

# タイルセットとタイルマップの作成
pyxel.tilemap(0).set(0, 0, [
    "0000222222222222",
    "0000000000000002",
    "0000000000000002",
    "0000000000000002",
    "0000000000000002",
    "1111111111111112"
])

pyxel.image(0).load(0, 0, "assets/tileset.png")

camera_x = 0

def update():
    global camera_x
    
    if pyxel.btn(pyxel.KEY_RIGHT):
        camera_x = min(camera_x + 2, 160)
    if pyxel.btn(pyxel.KEY_LEFT):
        camera_x = max(camera_x - 2, 0)
    
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    pyxel.bltm(0, 0, 0, camera_x, 0, 160, 120)

pyxel.run(update, draw)
```

このコードでは、簡単な2Dプラットフォーマーの背景を作成しています。pyxel.tilemap().set()でタイルマップを定義し、pyxel.bltm()で画面に描画します。カメラの位置を変更することで、大きな世界をスクロールさせることができます。

## 第10章: テキスト表示

ゲーム内でテキストを表示することは、スコアの表示やダイアログの実装など、多くの場面で必要になります。Pyxelでは、簡単にテキストを表示できる機能が用意されています。

```python
import pyxel

pyxel.init(160, 120, title="テキスト表示")

score = 0

def update():
    global score
    
    if pyxel.btnp(pyxel.KEY_SPACE):
        score += 10
    
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    pyxel.text(10, 10, f"Score: {score}", 7)
    pyxel.text(10, 30, "Press SPACE to increase score", 6)
    pyxel.text(10, 50, "Press Q to quit", 8)

pyxel.run(update, draw)
```

このコードでは、pyxel.text()関数を使ってテキストを表示しています。スコア、操作説明、終了方法をそれぞれ異なる色で表示しています。スペースキーを押すとスコアが増加し、その変更がリアルタイムで画面に反映されます。

## 第11章: 状態管理

ゲームの状態管理は、タイトル画面、ゲームプレイ、ゲームオーバーなど、異なる画面や状況を制御するのに重要です。ここでは、簡単な状態管理システムを実装します。

```python
import pyxel

pyxel.init(160, 120, title="状態管理")

class Game:
    def __init__(self):
        self.state = "TITLE"
        self.score = 0

    def update(self):
        if self.state == "TITLE":
            if pyxel.btnp(pyxel.KEY_SPACE):
                self.state = "PLAYING"
        elif self.state == "PLAYING":
            self.score += 1
            if self.score >= 100:
                self.state = "GAMEOVER"
        elif self.state == "GAMEOVER":
            if pyxel.btnp(pyxel.KEY_SPACE):
                self.score = 0
                self.state = "TITLE"
        
        if pyxel.btnp(pyxel.KEY_Q):
            pyxel.quit()

    def draw(self):
        pyxel.cls(0)
        if self.state == "TITLE":
            pyxel.text(60, 60, "PRESS SPACE", 7)
        elif self.state == "PLAYING":
            pyxel.text(10, 10, f"Score: {self.score}", 7)
        elif self.state == "GAMEOVER":
            pyxel.text(60, 60, "GAME OVER", 8)
            pyxel.text(45, 70, "PRESS SPACE TO RESTART", 7)

game = Game()
pyxel.run(game.update, game.draw)
```

このコードでは、ゲームの状態を"TITLE"、"PLAYING"、"GAMEOVER"の3つに分けています。各状態に応じて異なる更新処理と描画処理を行うことで、ゲームの流れを制御しています。

## 第12章: 衝突判定の改善

より複雑なゲームでは、より精密な衝突判定が必要になります。ここでは、円と矩形の衝突判定を実装します。

```python
import pyxel
import math

pyxel.init(160, 120, title="衝突判定の改善")

class Circle:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius

class Rectangle:
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height

def check_collision(circle, rect):
    closest_x = max(rect.x, min(circle.x, rect.x + rect.width))
    closest_y = max(rect.y, min(circle.y, rect.y + rect.height))
    
    distance = math.sqrt((circle.x - closest_x) ** 2 + (circle.y - closest_y) ** 2)
    
    return distance < circle.radius

circle = Circle(80, 60, 10)
rectangle = Rectangle(100, 50, 40, 20)

def update():
    circle.x = pyxel.mouse_x
    circle.y = pyxel.mouse_y
    
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    pyxel.circ(circle.x, circle.y, circle.radius, 11)
    pyxel.rect(rectangle.x, rectangle.y, rectangle.width, rectangle.height, 8)
    
    if check_collision(circle, rectangle):
        pyxel.text(10, 10, "Collision Detected!", 7)

pyxel.run(update, draw)
```

このコードでは、円と矩形の衝突判定を行っています。マウスで円を動かし、矩形と衝突すると画面上にメッセージが表示されます。この方法を使えば、より複雑な形状の衝突判定も実装できます。

## 第13章: パーティクルエフェクトの拡張

パーティクルシステムをさらに拡張して、爆発のようなエフェクトを作成します。

```python
import pyxel
import random
import math

pyxel.init(160, 120, title="パーティクルエフェクト")

class Particle:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.vx = random.uniform(-2, 2)
        self.vy = random.uniform(-2, 2)
        self.life = 30

    def update(self):
        self.x += self.vx
        self.y += self.vy
        self.life -= 1

particles = []

def create_explosion(x, y):
    for _ in range(50):
        particles.append(Particle(x, y))

def update():
    if pyxel.btnp(pyxel.MOUSE_BUTTON_LEFT):
        create_explosion(pyxel.mouse_x, pyxel.mouse_y)
    
    for particle in particles[:]:
        particle.update()
        if particle.life <= 0:
            particles.remove(particle)
    
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    for particle in particles:
        pyxel.pset(particle.x, particle.y, 8 + particle.life % 8)

pyxel.run(update, draw)
```

このコードでは、マウスをクリックした位置で爆発のようなエフェクトを生成します。各パーティクルは独自の速度と寿命を持ち、時間とともに消滅します。色も徐々に変化するため、より動的なエフェクトが得られます。

## 第14章: 音楽システムの拡張

Pyxelの音楽システムを使って、より複雑な音楽を作成し、ゲーム内で再生します。

```python
import pyxel

pyxel.init(160, 120, title="音楽システム")

# 音楽の定義
pyxel.sound(0).set("C3", "T", "7", "N", 30)
pyxel.sound(1).set("E3", "T", "7", "N", 30)
pyxel.sound(2).set("G3", "T", "7", "N", 30)

pyxel.sound(3).set("C3E3G3C4", "N", "7654", "NFNF", 10)

music_playing = False

def update():
    global music_playing
    
    if pyxel.btnp(pyxel.KEY_SPACE):
        if not music_playing:
            pyxel.playm(0, loop=True)
            music_playing = True
        else:
            pyxel.stop()
            music_playing = False
    
    if pyxel.btnp(pyxel.KEY_1):
        pyxel.play(0, 0)
    if pyxel.btnp(pyxel.KEY_2):
        pyxel.play(1, 1)
    if pyxel.btnp(pyxel.KEY_3):
        pyxel.play(2, 2)
    
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    pyxel.text(10, 10, "Press SPACE to toggle music", 7)
    pyxel.text(10, 20, "Press 1, 2, 3 for sound effects", 7)
    pyxel.text(10, 30, "Music playing: " + str(music_playing), 8)

pyxel.music(0).set(0, 1, 2, 3)
pyxel.run(update, draw)
```

このコードでは、バックグラウンドミュージックと効果音を実装しています。スペースキーでバックグラウンドミュージックのオン/オフを切り替え、1、2、3キーで異なる効果音を再生できます。pyxel.music()関数を使って、複数の音をつなげてより複雑な音楽を作成しています。

## 第15章: ゲームの最適化とパフォーマンス向上

最後に、ゲームの最適化とパフォーマンス向上について考えます。ここでは、多数のオブジェクトを効率的に管理する方法を示します。

```python
import pyxel
import random

pyxel.init(160, 120, title="ゲーム最適化")

class Star:
    def __init__(self):
        self.x = random.randint(0, 159)
        self.y = random.randint(0, 119)
        self.speed = random.uniform(0.5, 2)

    def update(self):
        self.y += self.speed
        if self.y > 120:
            self.y = 0
            self.x = random.randint(0, 159)

stars = [Star() for _ in range(100)]

def update():
    for star in stars:
        star.update()
    
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    for star in stars:
        pyxel.pset(int(star.x), int(star.y), 7)
    
    pyxel.text(5, 5, f"Stars: {len(stars)}", 8)
    pyxel.text(5, 15, f"FPS: {pyxel.frame_count}", 8)

pyxel.run(update, draw)
```

このコードでは、100個の星を効率的に管理しています。各星は独自の位置と速度を持ち、画面の下端に達すると上端に戻ります。pyxel.pset()関数を使って星を描画することで、描画処理を最小限に抑えています。また、フレームレートを表示することで、パフォーマンスを監視できるようにしています。

以上で、Pyxelを使ったゲーム開発の基本から応用までを15章にわたって解説しました。これらの知識を組み合わせることで、より複雑で面白いゲームを作成することができます。Pyxelの簡単さと柔軟性を活かして、自分だけのユニークなゲームを作ってみてください。