13
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PyxelのゲームのBGMにオーディオファイルを使う

Last updated at Posted at 2025-12-21

Pyxelは扱いやすいプログラム言語であるPythonと低解像度の2Dドット絵を用いてゲーム制作ができるので、ゲーム制作入門にうってつけのゲームエンジンだと私は考えていますが、「敷居の低さ」という意味ではサウンド面が1つのハードルになるかもしれません。

Pyxel版MMLの登場

2025年になってPyxelはMMLをサポートするようになり、今年のアドカレでもMMLを題材にしたテーマが複数投稿されています。

これらの記事内でも紹介されていますが、Pyxel MML Studio ではWeb上で手軽に打ち込んで試すこともできるので、Pyxel MML StudioでMMl制作をして、できあがったらMMLをゲームに組み込む、といった手法で、以前より手軽に本格的な8ビット風サウンドを取り入れられるようになりました。

でもサウンドは作れないのでやっぱり素材使いたい

とはいえ、MMLも知らないしそもそも作曲なんて無理、まだ人に依頼するほどの段階でもないし... という方も当然いらっしゃると思います。

であれば、サウンドに関してはPyxelの機能にこだわらずオーディオ使っちゃいましょう!
オーディオなら素材もいろいろ手に入りますし、作曲ができる人に依頼もしやすいでしょう。

具体的には古からのPython用ゲームライブラリ 「pygame」のmixer機能だけ を使います。
ループ対応したmp3やoggのファイルを用意するだけで、BGMのループ再生、フェードイン・アウト、一時停止や再開などゲームでよく使う操作が手軽に実装できます。

ただ、残念ながら大きな欠点(制約)があります。
pygameはpyodide(pyxelでも利用している、pythonコードをWebで動かすライブラリ)に対応していないので、この記事で紹介するやり方で制作したゲームはWeb公開できません

サンプル

実際にリンクから手軽に操作してもらえるサンプルを用意したかったのですが上記事情によりできないので、以下の動画で動作サンプルをご覧ください。

手元で動かす場合は、以下からコードをダウンロードしてmain.pyを実行してください。

実現方法

サウンドクラスを用意し、その中でpygame.mixerを使った処理を行います。
今回のような小さいデモであればメインプログラムに組み込んでもよいのですが、独立したソースにしておくとで、いろいろなプロジェクトに流用可能になります。

from pygame import mixer


class Sound:
    def __init__(self, vol=0.6):
        mixer.init()
        self.file = None  # 再生中のファイル名
        self.pausing = False
        self.set_volume(vol)

    # 音量設定
    def set_volume(self, vol):
        self.vol = vol
        mixer.music.set_volume(vol)

    # BGM再生
    def play(self, file, fade_time=300):
        # まだ再生中かもしれないので止める
        mixer.music.stop()
        try:
            mixer.music.load(file)
        except Exception as e:
            print(e)
            return
        self.file = file
        self.pausing = False
        mixer.music.play(loops=-1, fade_ms=fade_time)

    # 停止
    def stop(self, fade_time=300):
        if fade_time:
            mixer.music.fadeout(fade_time)
        else:
            mixer.music.stop()

    # ポーズ/再開
    def toggle_pause(self):
        if not self.file:
            return
        if self.playing():
            mixer.music.pause()
            self.pausing = True
        else:
            mixer.music.unpause()
            self.pausing = False

    # 再生位置取得(ミリ秒:ループしても維持されます)
    def get_pos(self):
        return mixer.music.get_pos()

    # 再生中かどうか(True/False)
    def playing(self):
        return mixer.music.get_busy()

続いてメインプログラムです。
キーボードで再生/停止、一時停止/再開、音量の上下ができる程度の簡単なコードにしています。
assets.pyxresをロードしていますが中身は空っぽで、単にパレット(スーファミっぽい雰囲気を出そうと思って背景をグラデーションにしました。この記事の趣旨とは関係ないです)をセットすることが目的です。

import pyxel as px
from sound import Sound


class App:
    def __init__(self):
        px.init(256, 224, "Pyxel-with-Audio サンプル")  # スーファミっぽいサイズ
        px.load("assets.pyxres")  # パレット読み込み用
        self.bdf = px.Font("umplus_j12r.bdf")  # フォントファイル
        self.sound = Sound()
        px.run(self.update, self.draw)

    def update(self):
        if px.btnp(px.KEY_RETURN):
            if self.sound.playing():
                self.sound.stop()
            else:
                self.sound.play("sample.ogg")
        if px.btnp(px.KEY_SPACE):
            self.sound.toggle_pause()
        if px.btnp(px.KEY_UP, 10, 2):
            self.sound.set_volume(min(self.sound.vol + 0.05, 1.0))
        if px.btnp(px.KEY_DOWN, 10, 2):
            self.sound.set_volume(max(self.sound.vol - 0.05, 0.0))
        if px.btn(px.KEY_ESCAPE):
            px.quit()

    def draw(self):
        px.cls(0)
        sound = self.sound
        for y in range(224):
            c = y // 7
            px.line(0, y, 255, y, c)
        self.draw_text(40, 34, "【BGM再生サンプル】")
        self.draw_text(40, 66, "エンターキーで再生/停止")
        self.draw_text(40, 82, "スペースキーで一時停止/再開")
        self.draw_text(40, 98, "上下キーで音量調整")
        playing = sound.playing()
        state = "停止中"
        if playing:
            state = "再生中"
        elif sound.pausing:
            state = "一時停止中"
        self.draw_text(40, 130, f"再生状態:{state}")
        self.draw_text(40, 146, f"音量  :{int(sound.vol * 100)}")
        if playing or sound.pausing:
            self.draw_text(40, 162, f"ファイル:{sound.file}")
            pos = sound.get_pos() / 1000
            self.draw_text(40, 178, f"再生位置:{pos:.3f}")

    def draw_text(self, x, y, t):
        for i in range(2):
            c = (0, 33)[i]
            px.text(x, y + 1 - i, t, c, self.bdf)


App()

補足:Web版でオーディオ使いたい

pygame.mixerを使った方法ではWeb化できないと書きましたが、Web版ではオーディオを使うことができないわけではなくて、HTMLにタグを配置し、とPythonからJavascriptの関数を呼び出してaudioタグを鳴らす、という方法があります。(ちょいと面倒です。)

もしローカルとWeb両方で動かしたいなら、ローカルとWebで動作を切り分けて、ローカル動作時はpygame.mixer、Web動作時はaudioタグを使う、といった実装も可能ですね。

サウンド関連で悩んでいる人のヒントになったら幸いです。

13
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?