--- date: 2025-12-16 01:00 tags: Pyxel, Presentation, Python, Advent Calendar --- # Pyxelで作るレトロプレゼンスライド ## はじめに Pyxel版での表示をご利用ください。 ```{figure} assets/slide-pyxel.* :target: https://shimizukawa.github.io/pyxel-slide-2025/ Pyxelで作るレトロプレゼンスライド、で作ったスライド
https://shimizukawa.github.io/pyxel-slide-2025/ ``` 同じ内容の Sphinx-revealjs 版スライドもあります。 ```{figure} assets/slide-revealjs.* :target: https://shimizukawa.github.io/pyxel-slide-2025/revealjs/slide-ja.html Pyxelで作るレトロプレゼンスライド、のSphinx-revealjsビルド版
https://shimizukawa.github.io/pyxel-slide-2025/revealjs/slide-ja.html ``` ### この記事/スライドは? この記事はPythonでレトロゲームが作れる「[Pyxel]」のアドベントカレンダー、「[Pyxel Advent Calendar 2025]」の16日目の記事です。 よろしくお願いします。 [Pyxel]: https://github.com/kitao/pyxel [Pyxel Advent Calendar 2025]: https://qiita.com/advent-calendar/2025/pyxel 書いた人: @shimizukawa です。 ```{figure} assets/face-dot.png :scale: 50 ``` - 1990年頃にゲームを作りたくてプログラミングを始めました - 当時はC言語、ASM、DirectXとハードルが高くて未完ばかりでした - Pyxelで、あの頃作りたかったゲーム作りに再挑戦中です ### レトロプレゼンスライド、って何? - レトロプレゼンスライド - レトロゲームエンジンで動作する、プレゼンテーションスライド表示アプリケーションです。 - 作ったきっかけ - [あるイベント]で「1990年頃に作りたかったゲームをPyxelで作った」というプレゼンをしました。 - スライドを作りながら、それならプレゼンスライド自体もPyxelで作ったら面白いのでは、と思って作りました。 この記事では、その実装方法を紹介します。 [あるイベント]: https://www.freia.jp/taka/blog/2025/02/pyconshizuoka2024/index.html ## レトロプレゼンスライドのPyxel要素 Pixel関連の技術要素を紹介します。 - 日本語文字表示 - 画像バンク - 画像埋め込み表示 - ディザリングによるページ切替アニメーション - Pyxelアプリをスライド内に埋め込み ### 日本語文字表示 日本語BDFフォント(Bitmap Distribution Format)を使いました。 - テキストで定義された、文字コードに対応したビットマップ情報 - 採用 http://openlab.ring.gr.jp/efont/unicode/ - 日本語をUnicodeで扱えて、大きい文字サイズが用意されている - 不採用: TrueTypeフォントからBDFに変換するツール - 配布とライセンスの問題を避けたかったため ```python font_title = pyxel.Font("assets/b24.bdf") font_pagetitle = pyxel.Font("assets/b16_b.bdf") font_default = pyxel.Font("assets/b12.bdf") font_bold = pyxel.Font("assets/b12_b.bdf") font_italic = pyxel.Font("assets/b12_i.bdf") ``` ### 画像バンク 独自の画像バンクを複数使って競合を避けています。 - Pyxelの画像バンクを2つ使って、スライド切替アニメーション - スライド進捗に合わせたキャラクター描画用画像バンク - さらに、別の画像バンクに子アプリを描画してオーバーレイ ```python renderd_page_bank = [ pyxel.Image(WIDTH, HEIGHT), pyxel.Image(WIDTH, HEIGHT), ] player_image = pyxel.Image.from_image("assets/urban_rpg.png") ``` ### ditherによるページ切替アニメーション Pyxelの `dither` 関数を使って、2つのスライドを合成しながら入れ替え ```python new_img = self.get_rendered_img(self.page) old_img = self.get_rendered_img(old_page) # old pyxel.dither(rate) pyxel.blt(old_x, old_y, old_img, 0, 0, WIDTH, HEIGHT, 7) # new pyxel.dither(1 - rate) pyxel.blt(new_x, new_y, new_img, 0, 0, WIDTH, HEIGHT, 7) pyxel.dither(1) ``` こんな感じで動きます。 ```{figure} assets/pyxel-slide-dither.mp4 :class: controls Pyxelのdither関数を使ったスライド切替アニメーション ``` ### Pyxelアプリをスライド内に埋め込み 1/2 マウスを子アプリにフォーカスして、子アプリに制御を渡します。 ```{figure} assets/typinggame.* :scale: 100 :width: 300 :height: 165 ``` ### Pyxelアプリをスライド内に埋め込み 2/2 子アプリは画像バンクに書き込み、スライドにオーバーレイしてます。 ```{figure} assets/jumpman.* :scale: 170 :width: 220 :height: 160 ``` ## レトロプレゼンスライドのスライド要素 スライド関連の技術要素を紹介します。 - 参考: Markdownスライドレンダラー - 技術スタック - アーキテクチャ - レイアウト、表現、動作 - コードフェンス(+ハイライト) - 画像表示 ### 参考: Markdownスライドレンダラー スライド作りにMarkdown記法を使うと便利です。 レイアウトに悩まされず、公開や再利用も楽になります。 MarkdownをHTMLスライドに変換するツールはいくつかあります。 これらはレンダラーにHTMLを使っています。 - [Slidev] : Vue.jsベースのスライド作成ツール - [Remark.js] : ブラウザ上でMarkdownスライドをレンダリング - [Reveal.js] : HTMLスライドフレームワーク - [Sphinx] に組み込む [sphinx-revealjs] 拡張 今回は、こういったツールを参考に、Pyxelで実装しました。 [Reveal.js]: https://revealjs.com/ [Remark.js]: https://remarkjs.com/ [Slidev]: https://sli.dev/ [Sphinx]: https://www.sphinx-doc.org/ja/master/ [sphinx-revealjs]: https://sphinx-revealjs.readthedocs.io/ ### 技術スタック 利用ライブラリ - `markdown-it-py`: Markdownパーサー - `pygments`: コードハイライト 実装、記法、動作仕様は、以下を参考にしました。 - `myst-parser`: Markdown記法の拡張 - `sphinx-revealjs`: SphinxのReveal.js拡張 ### アーキテクチャ 1. 読み込み - `markdownit-py` でMarkdownをトークンに分割 - 木構造のトークンをスライド単位で保持 2. 描画 - 見ているページをVisitorパターンでレンダリング - 記法に合わせて文字をレイアウト - 2枚のバンクでスライドめくりアニメーション 3. 操作 - キーボード、マウス、パッドでページ移動 - Ctrl+R でMarkdownを再読み込み ### レイアウト、表現、動作 対応している記法 - ヘディング1,2,3 = スライド,セクション,ページタイトル - 番号なし,番号つき箇条書き - **強調**, *斜体*, `literal` - URL link: https://example.com - コードフェンス(+ハイライト) - 画像読み込み ( `{figure} file.png` ) - Pyxel App 読み込み ( `{figure} file.py` ) (HTMLのレイアウトエンジンは良く出来てるなあ...) ### コードフェンス(+ハイライト) - `pygments` の `RawTokenFormatter` でtokenize - リスト構造のTokenを for match case で順次レンダリング ```python hl = pygments.highlight(content, lexer, formatter) for line in hl.decode("utf-8").splitlines(): token, value = line.split("\t") match token: case "Token.Operator.Word": with with_color(self, 2, -1): self._text(value) ... case _: self._text(value) ``` ### 画像埋め込み表示 - `pyxel.Image.from_image` で画像を読み込み - ただし画面解像度が低いので、大きな画像は潰れてしまう ```python p = pyxel.Image.from_image(filename) x, y, w, h = self.x, self.y, p.width, p.height s = 0.5 self.img.blt( x - int(w * (1 - s) / 2), y - int(h * (1 - s) / 2), p, 0, 0, w, h, scale=s, ) ``` ## おわりに なぜか、今日もゲーム作りが進みません! - Pyxelでレトロ調プレゼンスライドを作りました - Markdown記法でスライドを作成できます - Pyxelで作ったゲームを埋め込んでプレゼンできます - ソースコードは以下のリポジトリにあります - https://github.com/shimizukawa/pyxel-slide-2025