注目の記事

【Pythonでゲームを作ろう!】レトロな2Dゲームを作ってみた!

2019.10.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

2019.10.30 追記:成果物がゲーム要素に乏しかったのでもう少しちゃんと遊べるものに改良しました。たくさんの方に読んでいただけて恐縮です。少しでも使い方の参考になれれば嬉しいです。

Pyxelとは

ピクセルアートのレトロな2Dゲームが作れるPythonライブラリです。

・仕様
- Mac, Windows, Linux対応
- 同時に再生できる音は4音
- 使用できる色は16色のみ、定義可能な64サウンド
- Python3によるコード記述
- 256x256サイズ、3画像バンク
- 256x256サイズ、8タイルマップ
- 任意のサウンドを組み合わせ可能な8ミュージック
- キーボード、マウス、ゲームパッド
- 画像・サウンド編集ツール
pyxel/README.ja.md at master · kitao/pyxel · GitHub

ドット絵がうてるツールや、音楽を作成できるツールも入っており、ゼロからゲームを作ることができます。
シンプルな造りで初心者でも簡単にゲームを作ることができる仕様になっています。

Install

必要なもの:
- Python3.7 以上
- Homebrew

brew install python3 sdl2 sdl2_image
pip3 install -U pyxel

pyxel/README.ja.md at master · kitao/pyxel · GitHub

サンプルを動かしてみる

サンプルをインストール

install_pyxel_examples

ファイルを指定して実行

cd pyxel_examples
python3 01_hello_pyxel.py

実行結果:

レトロな雰囲気がたまらないですね!
ドット風のフォントもかわいいです。

使ってみた

ドキュメントを読みながら色々試してみました。
アセットはサンプルプロジェクトの物をお借りしています。

猫を表示させる

import pyxel

class App:
    def __init__(self):
        pyxel.init(160, 120, caption="Hello Pyxel")
        pyxel.image(0).load(0, 0, "assets/cat_16x16.png")
        pyxel.run(self.update, self.draw)

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

    def draw(self):
        pyxel.cls(0)
        pyxel.blt(75, 45, 0, 0, 0, 16, 16)

App()

出力結果:

pixel.init(width, height, )

init(width, height, , [scale], [palette], [fps], [border_width], [border_color])
  • width、height: 画面サイズを指定(画面の最大の幅と高さは255)。
  • caption:ウィンドウのタイトルを指定
  • scale:表示倍率を指定
  • palette:パレット色を指定
  • fps:動作フレームレートを指定
  • border_width、border_color:画面外側のマージン幅と色を指定

pixel.run(update, draw)

Pyxelアプリを開始する関数。フレーム更新時にupdate関数、描画時にdraw関数を呼び出す。

猫の背景を透過

先ほど実行したサンプルでは猫の背景が透過していません。 pyxel.blt()の[colorkey]オプションで透過色を設定することができます。

先ほどの猫の画像は5の色が背景色なので、透過色に5を指定します。

pyxel.blt(75, 45, 0, 0, 0, 16, 16, 5)

実行結果:

Player(猫ちゃん)を動かす

キーボード操作で左右に猫ちゃんを動かしてみます。

import pyxel

class App:
    def __init__(self):
        pyxel.init(160, 120, caption="Hello Pyxel")
        pyxel.image(0).load(0, 0, "assets/cat_16x16.png")

        # Starting Point
        self.player_x = 72
        self.player_y = 16

        pyxel.run(self.update, self.draw)

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

    def update_player(self):
        if pyxel.btn(pyxel.KEY_LEFT) or pyxel.btn(pyxel.GAMEPAD_1_LEFT):
            self.player_x = max(self.player_x - 2, 0)

        if pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.GAMEPAD_1_RIGHT):
            self.player_x = min(self.player_x + 2, pyxel.width - 16)

        if pyxel.btn(pyxel.KEY_UP) or pyxel.btn(pyxel.GAMEPAD_1_UP):
            self.player_y = max(self.player_y - 2, 0)

        if pyxel.btn(pyxel.KEY_DOWN) or pyxel.btn(pyxel.GAMEPAD_1_DOWN):
            self.player_y = min(self.player_y + 2, pyxel.height - 16)


    def draw(self):
        pyxel.cls(0)

        # 猫ちゃんを描写
        pyxel.blt(
            self.player_x,
            self.player_y,
            0,
            0,
            0,
            16,
            16,
            5,
        )
App()

実行結果:

フルーツを出現させてみる

猫ちゃんがお腹が空いてしまってはかわいそうなのでフルーツを出してあげましょう。

アセットを追加

pyxeleditorを使ってアセットを作成します。

作成したアセットを利用する

pyxeleditorで作成したアセットは.pyxres形式のファイルにまとめられます。
使用したいアセットを指定して利用します。

指定方法:

blt(x, y, img, u, v, w, h, [colkey])
イメージバンクimg(0-2) の (u, v) からサイズ (w, h) の領域を (x, y) にコピーする。w、hそれぞれに負の値を設定すると水平、垂直方向に反> 転する。colkeyに色を指定すると透明色として扱われる
公式ドキュメントから引用

リンゴを利用したい場合は(16, 0)を指定します。

成果物

pyxel_examplesのプロジェクトを参考に当たり判定やスコア機能などを追加してみました。

ソースコード

丸ごと置いておきます。
ゲームプログラミングに関しては完全に初心者なので、冗長な記述は大目に見ていただければと思います。

from collections import deque, namedtuple
from random import randint
import pyxel

Point = namedtuple("Point", ["w", "h"])  # 猫の向き

UP = Point(-16, 16)
DOWN = Point(16, 16)
RIGHT = Point(-16, 16)
LEFT = Point(16, 16)

class App:
    def __init__(self):
        pyxel.init(160, 120, caption="Hello Pyxel")
        pyxel.load("assets/hello2.pyxres")
        self.direction = RIGHT

        # Score
        self.score = 0
        # Starting Point
        self.player_x = 42
        self.player_y = 60
        self.player_vy = 0
        self.fruit = [(i * 60, randint(0, 104), True) for i in range(4)]

        pyxel.playm(0, loop=True)
        pyxel.run(self.update, self.draw)

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

        for i, v in enumerate(self.fruit):
            self.fruit[i] = self.update_fruit(*v)

    def update_player(self):
        if pyxel.btn(pyxel.KEY_LEFT) or pyxel.btn(pyxel.GAMEPAD_1_LEFT):
            self.player_x = max(self.player_x - 2, 0)
            self.direction = LEFT

        if pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.GAMEPAD_1_RIGHT):
            self.player_x = min(self.player_x + 2, pyxel.width - 16)
            self.direction = RIGHT

        if pyxel.btn(pyxel.KEY_UP) or pyxel.btn(pyxel.GAMEPAD_1_UP):
            self.player_y = max(self.player_y - 2, 0)
            self.direction = UP

        if pyxel.btn(pyxel.KEY_DOWN) or pyxel.btn(pyxel.GAMEPAD_1_DOWN):
            self.player_y = min(self.player_y + 2, pyxel.height - 16)
            self.direction = DOWN


    def draw(self):
        # bg color
        pyxel.cls(12)

        # draw fruits
        for x, y, is_active in self.fruit:
            if is_active:
                pyxel.blt(x, y, 0, 16, 0, 16, 16, 5)

        # draw cat
        pyxel.blt(
            self.player_x,
            self.player_y,
            0,
            16 if self.player_vy > 0 else 0,
            0,
            self.direction[0],
            self.direction[1],
            5,
        )

        # スコアを表示
        s = "Score {:>4}".format(self.score)
        pyxel.text(5, 4, s, 1)
        pyxel.text(4, 4, s, 7)

    def update_fruit(self, x, y, is_active):
        if is_active and abs(x - self.player_x) < 12 and abs(y - self.player_y) < 12:
            is_active = False
            self.score += 100
            self.player_vy = min(self.player_vy, -8)
            pyxel.play(3, 4)

        x -= 2

        if x < -40:
            x += 240
            y = randint(0, 104)
            is_active = True

        return (x, y, is_active)

App()

所感

今回作成したものはとても簡単なものではありますが遊べるものを作ることができました。音楽を作るツールも内蔵されているので引き続き使ってみようと思います。

Reference