前置き(注意書き?)
- ほぼほぼ自分用の雑多な記事(メモ)その1です。
- 趣味のプログラミングによる、Pythonでフロントエンドが書けるようにならないかな?というライブラリ作成記録です。まだ全然完成していません。
- 99.9%以上の方は普通にTypeScriptとかの選択の方が好ましいと思っています。ほぼ趣味です。
- ずっと黙々とコーディングだけしていると個人制作のアプリやゲームのように途中で挫折しそうだったので、定期的にQiitaにアウトプットしていこうと思います。
なぜ作ろうと考え始めたのか
- 普段95%くらいの時間はPythonでお仕事をしています。
- Pythonは仕事内容的に外せないのですが、jsやHTMLなども少しは書く必要があり、それらに作業を切り替えた時のスイッチングコストが気になっています。
- そのため出来たらほぼほぼPythonで完結できたら楽だな・・・と思い始めました。
- TkinterなどのGUI用のPythonパッケージも色々ありますが、web用が欲しいと思いました。
- Tkinter(もしくは他のGUI用ライブラリ)が、本は消化したけどなんだか馴染めない・・・と感じたり、もっとお洒落にしたいと感じてしまったのもあります。
- Brythonとかのjsの代替として使えるPythonライブラリもありますが、なんだか最初のロードがもっさり?している印象を受けたので利用を見送りました。
- Pythonでフロントのロジックも書けたら、Python用に入れている各種Lintやら単体テストやらの多くをPython環境のみで完結できて楽で素敵だなと思いました。CI的に、TypeScriptのようにmypyやPyrightで型の恩恵を得られるというのもあります。
なにより黒魔術感があってコードを書いていて私がとても楽しいです(ほぼほぼこの理由依存)。
参考にするインターフェイス
古の時代に滅びたActionScript3.0という言語のインターフェイスを参考にしていきます。
Flash(現Animate CC)で使われていた言語ですね。ECMAScript 4とかの時代での、JavaとJavaScriptを合体させたような静的型付け言語となります。
参考 : Flash作品を残すために取り組んだこと
特にこれ!といった選択理由は無いのですが、デザインの学校を出て元デザイナー職をやっていたりで長いこと楽しく使っていたことや、当時のFlash作品(カヤックさんのwonderflだったりICSさんの作品だったり、派手な動きや映像表現のフルフラッシュサイトなど)が好きだったというのが大きい気がします。個人の好みですね(仕事ではないので好きにやろうかと)。
また、2020年でFlashのサポートが終了し、なんだか世の中で色々なFlash関係の取り組みやサイトなどが出てきたのを見ていたら何だか自分も少し何かしたくなった・・・という気持ちもあるかもしれません。
例えば過去のFlash作品をRustとWebAssemblyで動くようにするためのruffleなどの取り組みはとても良いなぁと。
なお、ちょっと似たような方向性としてHaxeという言語があります。こちらもインターフェイスはActionScript3.0っぽい感じですが、出力先が色々な言語に出力できる(C++とかJavaとかPythonとか)という変わった言語になります。こちらもマイナーな言語ですが2019年などにポケモン剣盾とかに使われていて話題になっていました。
参考 :
HaxeはHaxeの言語で書いてjsやC++などに出力する形となりますが、今回作っていこうと思うライブラリはPythonで書いてjsなどに出力する形となります。
ライブラリ名
ActionScriptを参考にしたPythonライブラリということでapyscriptとしました。
本記事で使うバージョン
以降の節で現時点までで書いてきた内容を記録としてアウトプットしておこうと思います。
日々書いたものを細かくアップデートでリリースしているのですが、本記事では0.3.6のバージョンの時点のもので進めていきます。ろくに機能は出来ていませんし、現時点だと諸々粗削りで無駄が多い実装になっています。
$ pip install apyscript==0.3.6
また、本記事ではPython3.8.5を使っています。
Stageとエントリーポイント・パブリッシュ関係
まずはFlashで一番ベーシックになるStage関係です。Flashコンテンツの出力先となる一番ルートのコンテナーのような要素になります。
また、エントリーポイントはよくある一般的なものに準じてmain関数で実行するみたいな形を今のところ想定しています。
最終的な出力(パブリッシュとかリリースビルド的な出力)は、最後に出力用の関数(今のところsave_expressions_overall_html
という関数のみ)を呼びだす形としてみました(将来色々な関数を書き足したい所です・・・)。
Stageをインスタンス化してエクスポートするようなところまで書くと以下のような形になります(main.pyというモジュール名を付けてあります)。
from apyscript.display.stage import Stage
from apyscript.html import exporter
def main() -> None:
"""Entry point of this project.
"""
stage: Stage = Stage(
stage_width=300, stage_height=185, background_color='#333')
exporter.save_expressions_overall_html(
dest_dir_path='./output')
if __name__ == '__main__':
main()
以下のようにCLIでmain.pyを実行してみると、Flashファイルでパブリッシュした時のようにHTML(index.html)やらjsやらが指定したディレクトリパス以下に出力されます。
$ python main.py
出力されたindex.htmlをブラウザで開いてみると、以下のようにStageのコンストラクタで指定した幅300px高さ185px背景色#333
の要素が出力されていることを確認できます。
Sprite
SpriteはActionScript3(以降AS3)でStageと同様に基本的なクラスとなります。コンテナー的な機能を持ち、且つベクターグラフィックなどの描画の機能(graphics属性)なども持ちます。
apyscriptでは今のことろコンテナとしてはadd_child
などのインターフェイスや、graphics
属性による描画関係(塗り設定のbegin_fill
や線設定のline_stype
、四角の描写のdraw_rect
など)を追加しています。
from apyscript.display.stage import Stage
from apyscript.display import Sprite
from apyscript.html import exporter
def main() -> None:
"""Entry point of this project.
"""
stage: Stage = Stage(
stage_width=350, stage_height=200, background_color='#333')
sprite: Sprite = Sprite(stage=stage)
stage.add_child(sprite)
sprite.graphics.begin_fill(color='#0af')
sprite.graphics.draw_rect(x=50, y=50, width=100, height=100)
sprite.graphics.line_style(color='#fff', thickness=2)
sprite.graphics.draw_rect(x=200, y=50, width=100, height=100)
exporter.save_expressions_overall_html(
dest_dir_path='./output')
if __name__ == '__main__':
main()
コードを実行して出力されたHTMLをブラウザで見てみると以下のように四角が2つ出力されていることが確認できます。
$ python main.py
これだけでもとても楽しい。
ちなみにこの辺りの描写や親子関係の制御は今の所SVGを利用しています。
xとyの座標更新関係なども対応しています(回転制御などはまだ未対応。そのうち作りたい所です)。
...
from apyscript.type import Int
from apyscript.display.rectangle import Rectangle
...
sprite.graphics.line_style(color='#fff', thickness=2)
rectangle_2: Rectangle = sprite.graphics.draw_rect(
x=200, y=50, width=100, height=100)
sprite.y = Int(0)
rectangle_2.y = Int(100)
※Intなどに関しては後の節で触れます。
コンソール出力のtrace
Pythonのprintやlogging、jsのconsole.logに当たるものがAS3だとtraceという関数になりますので、そのままtrace関数を実装してあります。
...
from apyscript.console.trace import trace
...
rectangle_2.y = Int(100)
trace('Hello apyscript!')
trace('rectangle.y:', rectangle_2.y)
出力して確認してみると、ブラウザのコンソールに出力されていることが確認できます。
基本的なデータのクラス : Int, Number, String, Boolean
整数や文字列などの基本的なデータのクラスについて以降の節では触れていきます。本当はPythonのビルトインのもの(intとかfloatとか)だけ扱っていればそのままjs側にも反映される・・・のが理想だったのですが、しばらく考えていてスマートにいく方法が思いつかなかったので独自クラスで設けてあります(将来イベントリスナーとかで各フレームでずっと更新され続ける変数・・・みたいなものを組むとどうしてもjs側で変数として反映される必要があるのかなと)。
PythonとAS3を比べると、各クラスが以下のように名前が異なります(intのみ被っています)。
- 整数 : int ⇔ int
- 浮動小数点数 : float ⇔ Number
- 文字列 : str ⇔ String
- 真偽値 : bool ⇔ Boolean
int以外のクラスはAS3側のクラス名をそのまま使うとして、intに関してはそのままの名前で実装するとPythonビルトインのものを上書きしてしまうので、Intと先頭を大文字にしました(他のクラスもAS3側は大文字開始が多いですし)。
演算などのインターフェイスは基本的にPythonのビルトインのものと比べてそこまで違和感が出ないようにしてあります。
なお、この記事を書いていて日本語をtraceの引数に放り込むと文字化けしていて、「あ、HTMLにcharset設定していないじゃん・・・」と気づいたので近日対応します。ずっと英語で作業していたのでうっかり気づきませんでした・・・;(#31)
from apyscript.display.stage import Stage
from apyscript.html import exporter
from apyscript.console.trace import trace
from apyscript.type import Int
def main() -> None:
"""Entry point of this project.
"""
stage: Stage = Stage(
stage_width=350, stage_height=200, background_color='#333')
int_value: Int = Int(10)
trace('Integer initial value:', int_value)
int_value = int_value + 10
trace('After 10 addition:', int_value)
other_int: Int = Int(20)
int_value += other_int
trace('Incremental addition result: ', int_value)
trace('Equal comparison:', int_value == 40)
exporter.save_expressions_overall_html(
dest_dir_path='./output')
if __name__ == '__main__':
main()
...
from apyscript.type import String
...
string: String = String('Hello')
trace('Initial string:', string)
string += ' apyscript!'
trace('String concatenation result:', string)
trace('Equal comparison:', string == 'Hello apyscript!')
NumberとBooleanも似たような感じで組んであります(長くなるのでここでは割愛)。
ブラウザ上でのテスト用の簡易のインターフェイス
実装が多くなってくるとjs側にちゃんと変換されているかな?というチェックが少し大変になってきました。がっつりとしたフロントのテストライブラリとかまでは今の所必要無いのですが、その辺りが楽にできるように生のjsのconsoleの機能を使って簡易のアサーションができるようにしました。
assert_equalとかassert_trueといった、良くある名前のインターフェイスにしています。
...
from apyscript.console.assertion import assert_equal
from apyscript.console.assertion import assert_true
...
int_value: Int = Int(10)
assert_equal(expected=10, actual=int_value)
assert_true(int_value == 10)
assert_equal(expected=11, actual=int_value)
所感と今後の予定など
- とりあえず手を出してみましたが、久々にAS3のドキュメントなどを読んでいたらそのインターフェイスの膨大さに結構圧倒されました。全部は勿論再現しませんが、途中で挫折しないかとても心配です(飽きやすい人間なので)。
- 好きな技術の組み合わせで自由にコードを書いていけるのは、やはりプライベートならではなのでとても楽しいです。無限にプライベートの時間が溶けます。
- 長期戦にはなるのは確定なので、何とか挫折したり途中放棄しないように気を付けていきたいところです(個人ゲーム開発などと同様で途中放棄が一番の敵)。飽きたら別のものでしばらくお休みして遊んだりしつつも、その後に作業に復帰したいところです。
- 途中放棄していなければ、数か月後に#2をまた書こうと思います。
- 直近ではArray、Dictionary、if文、for文などの基本部分をやったら描画系のインターフェイスやイベントリスナーとかも段々と手を出していきたいところです。
- ずっと先の話になりますが、Processing関係(p5.js)も組み込めたら面白そうだなと思っています。p5.js本をそのうち消化したいところです。
- Bitmap(BitmapData)とかでの画像処理とかに関してはcanvas関係を学べばいいのだろうか・・・その辺も詳しくないので、将来本を買って勉強しようと思います。
- まだ現時点だとろくに機能がありませんが、この時点でもぼちぼち作業量が多くなってきています(結構試行錯誤している感じもあります)。あまり慌てずに、長期的にちまちまと楽しみつつやっていきたいと思います。