はじめに
malo21st さんの「Pyxel × Pymunkで物理シミュレーションを始めよう! (Pyxel Advent Calendar 2024 の 15日目の投稿)」を「Webで動かしたい」という動機の投稿です🌎
この投稿は、malo21st 様の投稿の上に成り立っております。
素晴らしい投稿ありがとうございます👾👾👾
Web実行版
若干の紆余曲折はありましたが、Web上で動かすことが出来ました。
以下はお試し用のURLと実行例の画像です。
Web実行版URL:https://kazuhito00.github.io/pyxel-pymunk-web-test/
今回試したソースコードは以下リポジトリで公開しています。
以降は紆余曲折部分の解説です🦔
Pyxel Web版で使用可能なパッケージ
Pyxelユーザーの方には、おなじみだと思いますが、PyxelはWeb実行をサポートしています。
ただし、Web実行はPyodideの仕組みを利用しており、使用できるパッケージに制限があります。
具体的には、Pyodide の Packages built in Pyodide のページに記載のパッケージが使用できます(+純粋にPythonのみで書かれたパッケージ)
別件で、このページを眺めた時に Pymunk は無かった気がしましたが、、、
改めて確認しても、やはり Pymunk を探してもリストにはありませんでした。
(b2dという少々マイナー?な物理エンジンはサポートされていましたが)
ダメ元でPymunk公式を確認
Pyodide公式にサポートされていないのであれば、望み薄であると思いながら、
ダメ元でPymunk公式に何か情報は無いか探しにいったところ、、、
Wasm wheelがリリースされていました👀
※PyodideはCPythonをWebAssembly/Emscriptenに移植したもので、Wasm wheelがあればパッケージを動かせる可能性あり
リリースを見に行くと
明らかに、Pyodide向けっぽい命名がされていました。
Pymunk Wasm wheelを動作確認
Pyodide向けっぽい命名だったので、期待しつつ、
とりあえず Pymunk単体で動作確認をしてみようと、以下のスクリプトを用意して確認しました。
try:
import micropip
print("micropip successfully imported")
except ImportError as e:
print(f"Error importing micropip: {e}")
try:
await micropip.install("pymunk-6.9.0-cp312-cp312-pyodide_2024_0_wasm32.whl")
print("Pymunk installed successfully")
except Exception as e:
print(f"Error installing Pymunk: {e}")
try:
import pymunk
print("Pymunk successfully imported")
except ImportError as e:
print(f"Error importing Pymunk: {e}")
<!DOCTYPE html>
<html lang="ja">
<head>
<title>Test</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://pyscript.net/releases/2024.11.1/core.css">
<script type="module" src="https://pyscript.net/releases/2024.11.1/core.js"></script>
</head>
<body>
<py-config src="./pyscript.toml"></py-config>
<py-script src="./main.py"></py-script>
</body>
</html>
packages = ["micropip", "cffi"]
name = "Test"
実行確認は簡易HTTPサーバー( http://localhost:8000 )で動作確認↓
python -m http.server 8000
しかし、実行したところ「Error installing Pymunk: Requested 'cffi>=1.17.1', but cffi==1.16.0 is already installed」と言うエラーが出てしまいました。
Pyodideのサポートしているパッケージバージョンと、Pymunkの要求するパッケージバージョンが食い違ってしまっていますね👀
Pymunk公式へIssueで問い合わせてみる
「サポート対象外です」とか言われたら、サクッと諦めようかと思いつつ、、、
とりあえずエラー内容をIssueで問い合わせてみました。
問い合わせてみたら、サクッと対応版がリリースされてしまいました。
上記のお試しスクリプトで6.10.0版をインストール・インポートすると、問題無く動作しました。
viblo様、ありがとうございます👾👾👾
Web実行に向けて「3_2_shot_bullet.py」をちょっと改造
Web実行に向けてオリジナルの「malo21st/Pyxel_Pymunk/3_2_shot_bullet.py」をちょっと改造しています。
Web実行では、Pymunkをインストールする処理が必要になります。
今回は、ファイル冒頭でインポートしないようにして、Appクラスの生成直前でインポートして、インポートしたインスタンスをAppクラスに渡すように変更しています(もっと良い方法あるかも)
このようにした理由は以下2点です
・Pymunkのインストールが完了する前に、インポートしてしまうとエラーになるため
・「if __name__ == "__main__":」を利用して、ローカルPC上での実行と、インストールと言う前処理が必要なWeb上での実行を切り分けるため
import pyxel
class App:
def __init__(self, pymunk, fps=60):
self.pymunk = pymunk
self.fps = fps
pyxel.init(256, 256, fps=fps, title="Vertical Stack with Pyxel")
pyxel.load("shot_bullet.pyxres")
self.create_world()
pyxel.run(self.update, self.draw)
def create_world(self):
self.space = self.pymunk.Space()
self.space.gravity = 0, 900
self.space.sleep_time_threshold = 0.3
# Static lines
static_lines = [
self.pymunk.Segment(self.space.static_body, (10, 200), (240, 200), 1),
self.pymunk.Segment(self.space.static_body, (230, 200), (230, 50), 1),
]
for line in static_lines:
line.friction = 0.3
self.space.add(*static_lines)
# Stacked boxes
self.shapes = []
for x in range(5):
for y in range(10):
size = 10
mass = 10.0
moment = self.pymunk.moment_for_box(mass, (size, size))
block_body = self.pymunk.Body(mass, moment)
block_body.position = 100 + x * 20, 100 + y * (size + 0.1)
block_shape = self.pymunk.Poly.create_box(block_body, (size, size))
block_shape.friction = 0.3
self.space.add(block_body, block_shape)
self.shapes.append(block_shape)
# Bullets
self.bullets = []
def update(self):
step = 5 # Run multiple steps for more stable simulation
step_dt = 1 / self.fps / step
for _ in range(step):
self.space.step(step_dt)
if pyxel.btnp(pyxel.KEY_SPACE):
mass = 100
r = 5
moment = self.pymunk.moment_for_circle(mass, 0, r, (0, 0))
bullet_body = self.pymunk.Body(mass, moment)
bullet_body.position = (10, 165)
bullet_shape = self.pymunk.Circle(bullet_body, r, (0, 0))
bullet_shape.friction = 0.3
self.space.add(bullet_body, bullet_shape)
self.bullets.append(bullet_shape)
impulse = 150_000
bullet_body.apply_impulse_at_local_point((impulse, 0), (0, 0))
if pyxel.btnp(pyxel.KEY_Q):
pyxel.quit()
if pyxel.btnp(pyxel.KEY_A):
self.create_world()
def draw(self):
pyxel.cls(0)
# Draw bullets
for bullet_shape in self.bullets:
x, y, *_ = bullet_shape.bb
angle = -bullet_shape.body.angle * 180 / 3.14 # radian => degree
pyxel.blt(x, y, 1, 0, 0, 10, 10, rotate=angle)
# Draw boxes
for block_shape in self.shapes:
x, y, *_ = block_shape.bb
angle = -block_shape.body.angle * 180 / 3.14 # radian => degree
pyxel.blt(x, y, 0, 0, 0, 10, 10, rotate=angle)
# Draw static lines
pyxel.line(10, 200, 240, 200, 7)
pyxel.line(230, 200, 230, 50, 7)
# Draw Key explanation
pyxel.text(4, 4, "SPACE: Shot Bullet", 7)
pyxel.text(4, 12, "A : Reset", 7)
if __name__ == "__main__":
import pymunk
FPS = 60
App(pymunk, FPS)
Web版ではなく、ローカルPC上で単体動作させる時は以下です↓
python shot_bullet.py
Web用の「web_test.py」と「index.html」を用意
こちらも、もっと良い書き方がありそうですが、Web実行用に実行スクリプトを用意しています(await使う都合上、ちょっとゴチャっとしています)
micropipを利用してPymunkをインストールした後、Appクラスを初期化して実行しています。
micropipでインストールするwhlファイルはダウンロードして、同ディレクトリ内に格納してください。
import asyncio
import micropip
from shot_bullet import App
# メイン関数を非同期で実行するための関数
def run_asyncio():
# 現在のイベントループを取得
loop = asyncio.get_event_loop()
# イベントループが実行中か確認
if loop.is_running():
# 実行中の場合は、非同期タスクとしてmain()を登録
asyncio.ensure_future(main())
else:
# 実行中でない場合は、新しいイベントループでmain()を実行
asyncio.run(main())
# 非同期のメイン関数
async def main():
try:
# micropipを使ってwhlファイルからpymunkをインストール
print("Installing Pymunk...")
await micropip.install("pymunk-6.10.0-cp312-cp312-pyodide_2024_0_wasm32.whl")
print("Pymunk installed successfully")
# pymunkをインポート
import pymunk
# Appクラスを初期化して実行
App(pymunk)
except Exception as e:
print(f"Error: {e}")
# メイン関数を実行
run_asyncio()
index.html は、公式サンプルとほぼ同じですが、micropipを利用する都合上、
「packages="micropip"」を、pyxel-playタグに追加しています。
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/gh/kitao/pyxel/wasm/pyxel.js"></script>
</head>
<body>
<pyxel-play root="." name="pyxel-pymunk-web-test.pyxapp" packages="micropip"></pyxel-play>
</body>
</html>
Pyxelパッケージ化を行い、簡易HTTPサーバー( http://localhost:8000 )で動作確認↓
※パッケージ化する対象スクリプトは「shot_bullet.py」ではなく「web_test.py」
※Web用ではないパッケージ化の場合は「shot_bullet.py」を対象とする
pyxel package ./ web_test.py
python -m http.server 8000
以上。