play-clj のインストール、シンプルなウィンドウの表示
arcade-clj シリーズ 1 つ目。シリーズ全体の目次はこちら。 一部以前の記事と重複している所もあるけどご容赦を。
- play-clj のインストール
- ひとまず動かしてみる
- 元記事の仕様に合わせる
- REPL で動かしてみる
- 最後の機能追加
- play-clj REPL の制限
- nREPL サーバは jack-in しないで connect する事のススメ
- github スナップショット
play-clj のインストール
インストールと言うか leiningen での新規プロジェクト作成時にテンプレートで play-clj を指定するだけで良い。
lein new play-clj arcade-clj
プロジェクトが作成されたら desktop/project.clj
の Clojure のバージョンを 1.8.0 に変更して
lein deps
すれば Clojure 含め必要なライブラリは全てインストールされる。
Clojure は別にこれと言って新しくする理由もないんだけど何となく。
*1
ひとまず動かしてみる
play-clj は lein run
するととりあえず Window が出るまでの最低限のテンプレートを生成するので
まずは lein run
で動かしてみる。黒いウィンドウが出てきて左下に "Hello world!" と出ていれば OK。
元記事の仕様に合わせる
元記事は、
- タイトルに "ArcadeRS Shooter"
- 画面解像度 横 800, 縦 600
- 真っ黒な Window
- 3 秒経ったら自動的に終了する
となっているので合わせる。
まず、1,2 を合わせる為に desktop/src/arcade_clj/core/desktop_launcher.clj
を開く。
そこの
(LwjglApplication. arcade-clj-game "arcade-clj" 800 600)
を
(LwjglApplication. arcade-clj-game "arcade-clj Shooter" 800 600)
とする。解像度は生成されたテンプレートのデフォルトと一致してたのでそのまま。
REPL で動かしてみる
さて、Clojure を使っていて REPL を使わない手はない。
コマンドプロンプトを立ち上げて lein repl :headless
として nREPL サーバを起動する。
(cider-jack-in しない理由は後述する)。
次に emacs から desktop/src-common/arcade_clj/core.clj
を開き
cider-connect
(C-c M-c
)でさっき起動した nREPL サーバにつなぐ。
そして REPL から
arcade-clj.core.desktop-launcher> (-main)
とタイプし起動。project.clj
を見ると分かるが、main は ns が desktop-launcher にあるので
別の ns で実行しても動作しない為注意。実行した結果、lein run
した時と同じ画面が出てくると思う。
正直ここまでだと lein run
の方が楽じゃないのと思うだろうがここからが違う。
じゃ次に元記事に仕様を合わせるべく次に画面を真っ黒にするためラベルを消してみる。
それは簡単で defscreen
の :on-show
で指定されている label の S 式全体を消してしまえば良い。
:on-show (fn [screen entities] (update! screen :renderer (stage)))
こんな感じに。
で、ソースを保存し C-c C-k
(cider-load-buffer) した後、nREPL クライアントで
namespace を arcade-clj.core に移動した後で以下のコマンドを実行する。
(on-gl (set-screen! arcade-clj-game main-screen))
上記はざっくりと言えばゲーム画面に変更を反映させる為のコマンドになる。 このコマンドを実行すると画面から"Hello World!"はなくなり真っ黒になったはず。
それでもまだ面倒
正直自分が面倒だったのでさっきの手順をひとまとめに実行する emacs のコマンドを作った。
init.el
辺りにこんなコードを入れておく。
(defun play-clj-reload () "REPL へ play-clj への反映コマンドを投げる。 arcade-clj-game と main-screen は適時書き換える事。" (interactive) (cider-interactive-eval "(on-gl (set-screen! arcade-clj-game main-screen))")) (defun save->eval->reload () "play-clj での開発用。バッファセーブ→バッファ評価→play-cljのリロードのコンボをまとめて実行する。 sit-for で wait かましているのはあんまり意味ないかも。" (interactive) (save-buffer) (sit-for 1) (cider-load-buffer) (sit-for 1) (play-clj-reload)) (define-key cider-mode-map (kbd "<f5>") 'save->eval->reload)
これで F5 キーを押すだけで「セーブ→セーブしたバッファの評価→ゲーム画面に反映」まで ひとまとめにやってくれる。
ただこれだとこの記事で作ったプロジェクト以外にする度に書き換える必要があるのが課題ではあるが どうすればもう少し汎用的になるかはまだ調べていない。
emacs の設定書き換えるの嫌なんですが
これはもう emacs から離れて NightMod 辺りを使うしかないと思う。 こちらは先のコマンド登録相当の事を機能として実装してるとの事なので試してみてはどうだろうか。
自分は NightMod 自体を試してない事から比較判断はできない。
最後の機能追加
さて、最後に 3 秒経ったら終了するコードを追加する。
(defscreen main-screen :on-show (fn [screen entities] (update! screen :renderer (stage)) (add-timer! screen :auto-dispose 3) ;; (1) entities) ;; ?? :on-timer ;; (2) (fn [screen entities] (case (:id screen) :auto-dispose (app! :exit))) :on-render (fn [screen entities] (clear!) (render! screen entities)))
これを評価すると 3 秒経った後画面が勝手に閉じる。 元記事と少し違ってタイマーを使って終了するようにした。
(1) で :auto-dispose
と言う keyword で 3 秒後に実行されるタイマーを登録し、
タイマーが登録されると (2) の :on-timer イベントハンドラ関数が実行されるようになり、
screen の中にある :id が :auto-dispose ならアプリを終了するコードを追加している。
また、今回の機能追加に直接関係は無いが、?? の部分を補足する。
play-clj は各イベントハンドラ関数の戻り値としては entity が返される事を想定しており、
add-timer!
関数が返す Timer は entity としては扱えないので最後に持ってくるとエラーになる。
play-clj の leiningen テンプレートでは label
を返すようになってたが、
これは entity として扱われるので OK だった。
とりあえず今回は怒られないようにする為だけにイベントハンドラで受け取った entities を
そのまま返すようにした。中身は何も無いんだけどね。
entity system は play-clj 固有の概念なのだが、この辺りは 実際にもう少しちゃんと扱うようになったらまた改めて説明しようと思う。
play-clj REPL の制限
さて、アプリが自動終了した後 REPL でもう一度 (-main)
として起動させようとして nil
だけ返ってきてあれ?
と思ったかもしれない。これは play-clj と言うか libGDX の制限で、
play-clj のゲーム画面と言うのは OpenGL コンテキストスレッド上で動作するのだが、
OpenGL コンテキストスレッドは 1 つのプロセスに対し 1 回しか起動を許していないという制限がある。
コマンドプロンプトの方を見るとそれらしき事を示す例外が吐き出されている。
Exception in thread "LWJGL Application" java.lang.RuntimeException: No OpenGL context found in the current thread.
自動終了するようコードを追加しておいてアレだが、アプリが終了した後は nREPL サーバを再起動しないともう起動できない。
nREPL サーバは jack-in しないで connect する事のススメ
また、この記事の最初の方でわざわざコマンドプロンプトを立ち上げて nREPL サーバを別に立ち上
げたのには理由があり、cider-jack-in した場合だと先のアプリが終了した時などの
例外を見えるようにする為というのが大きい。アプリ終了後に(-main)
としても
cider の nREPL クライアント側では nil
としか返ってなかった事からも分かると思う。
例外が全く見えなくなってしまうのはさすがに辛いので play-clj で開発する際は
コマンドプロンプトで nREPL サーバを立ち上げてから cider-connect
する方法を強くオススメする。
また、アプリは終了せずとも :on-render
などで大量に例外が吐き出されるような事も
開発中は起きてくるだろう(と言うか自分が体験した)。
例外が少ない場合はまだいいが、大量に例外が出続けると最初の方の例外はコマンドプロンプトだと
流れてこれも見えなくなってしまう。
このような場合は nREPL サーバ起動時にエラー出力をリダイレクトさせておくと良い。
> lein repl :headless 2> err.txt
こんな感じで。
github スナップショット
今回の記事終了時点のソースは以下から取得可能。
Release v0.1.0 · shinmuro/arcade-clj · GitHub
*1:強いて上げれば 1.8.0 で追加された direct linking が play-clj で使えるのか試してみたいくらい
ArcadeRS サンプルゲームを通じて play-clj に慣れる
ArcadeRSという ゲーム制作を通じての Rust 言語を学ぼうと言うチュートリアル記事がある。
この記事はそのチュートリアルを通じて完成するゲームを play-clj で作ってみようと言う記事。 元記事と違って Clojure そのものは既にある程度学習済で、ここのタイトルにあるように play-clj、引いてはベースとなる libGDX の使い方に慣れるのが主な目的になる。 なので、Clojure 自体の入門や開発環境の構築は他書籍やサイトを参考にして下さい。
この記事を作成するにあたっての開発環境だけは書いておきます。
- Windows7 Pro 64bit
- JDK 7u45(1.7.0_45)
- Clojure 1.8.0
- leiningen 2.6.1
- Emacs 24.5(NTEmacs64 IMEパッチ適用版)
- cider 0.12.0
- play-clj 1.1.0
流れだけは元記事に沿うようにするが、翻訳記事ではないので元記事のリンクを載せたりもしない。
また、後で検索しやすいよう arcade-clj
とタグ付けしておく。
完成イメージ
現在は元記事のスクリーンショットをそのまま持ってきているだけだが完成したら差し替える予定。
と言っても見た目は多分タイトルバーデザインが Windows のものになるだけだと思う。
目次
日本語になっていない所はまだ play-clj 版での記事になっていない部分。 また、機能ベースでタイトルを付けていく予定。
- play-clj のインストール、シンプルなウィンドウの表示 - 日曜プログラミング
- ESC を押したら終了するようにする(イベントハンドリングの触り) - 日曜プログラミング
More event handling, where we discuss macrosViews, where we learn about boxes, pattern matching, trait objects, and dynamic dispatch- 3,4 は Rust の言語機能がメインで、特にゲームとしての機能追加を行ってるわけではないので割愛。
- Screen の 切替 - 日曜プログラミング
- 四角形を動かす、描画する - 日曜プログラミング
- Sprites, where we create our player’s ship
- Backgrounds, where we handle resizing, scale and translate through time
- Main menu, where we play with textures and Rust’s vectors
- Asteroid attack!, where we render animated asteroids
- Shooting bullets, where we explore iterators
- Brawl, at last!, where we make objects interact and explode
- ≪ Boom! ≫, where we play sound.
- Variety, where we create more enemies
- Difficulty, where we manage the difficulty level and the score
- High score & wrap-up, where we play with the file-system and enhance our main menu
目次だけ最初に掲げちゃって大丈夫かと自分でも少し思うが ペースは遅くとも何とか最後までいけるよう頑張ります。
勿論チュートリアルのゲームを完成させるのが一番の目的だけど 記事の方も日本語に置き換えられて*1リンクが貼られれば完成とします。 また、この記事は主に目次なので記事を更新次第リンクとタイトルも更新します。
*1:翻訳じゃなくあくまで機能ベースの置き換え
cider で play-clj 触り始めました
libGDX を Clojure 上でより使い易くされた play-clj 触り始めました。
まだサンプル触ってる段階だけど REPL で起動したままいじれるのは 非ゲーム分野では経験済みとは言えゲームでも可能なのが改めて新鮮。
今日は 公式チュートリアル ではフォローされてない emacs+cider 使った時の補足事項みたいなのをいくつか。
cider で REPL
公式チュートリアルで流れは書かれてるがもう少し細かく言えば ソースをいじって REPL から起動したゲーム画面に反映させるには、
の作業が必要で、1. は1回でいいが、2~4 は繰り返す事になり、 これをコマンド+タイピングで毎度やるのはさすがに煩わしい。
なので emacs にコマンドとしてキー1発で呼び出せるものをてきとーに書いた。
(require 'cider) (defun play-clj-reload () "REPL へ play-clj への反映コマンドを投げる。 hello-world-game と mainscreen は適時書き換える事。" (interactive) (cider-interactive-eval "(on-gl (set-screen! hello-world-game main-screen))")) (defun save->eval->reload () "play-clj での開発用。バッファセーブ→バッファ評価→play-cljのリロードのコンボをまとめて実行する。 sit-for で wait かましているのはあんまり意味ないかも。" (interactive) (save-buffer) (sit-for 1) (cider-load-buffer) (sit-for 1) (play-clj-reload)) (define-key cider-mode-map (kbd "<f5>") 'save->eval->reload)
docstring にも書いたが sit-for は別になくてもいいかも。 まあけどこれでF5一発で画面反映までしてくれるようになるので結構楽になる。
cider-jack-in だとランタイムエラーが出ない?
play-clj(と言うか libGDX) は GL スレッドを 1 個作ってそこでイベントハンドリングするという まあ Swing や JavaFX などと同じような手法を取ってるのだが、この手のものは cider-jack-in した場合別スレッドを立ち上げる為か(詳しくは調べてない)、 例外のメッセージを吐き出さずに止まってしまう事がある。
play-clj はその場合にも対応できる方法を用意してて、set-screen-wrapper!
を使って
例外時は printStackTrace して空の画面を出させるようにするというスニペットをチュートリアルの
一番最後に載せてあり、それを core.clj の一番最後に入れておけば大抵 REPL を再起動せずとも
復帰できるようになる。
ただ、それでも cider-jack-in でコンソールを立ち上げずに REPL 起動した場合だと エラー時もやはりメッセージが出てくれない。
ウィンドウは増えてしまうがコマンドプロンプトから REPL 立ち上げるとエラーメッセージは 出るので個人的にはこちらの方をオススメする。
ちなみにここでも前述した emacs コマンド(F5) で復帰できて便利。
駄文:すいません、Rust 俺にはムリ
まあ誰に謝ると言う訳でもないんだけど Rustはプログラミング言語Rustを 一通り試してみて、さあそれじゃちょっとしたツールやライブラリみたいなの試しに作ってようかと 手を動かしてみたらコンパイラに怒られまくって正直心折れました。
いや、ネイティブコード吐き出す言語はちゃんと手を出した事がなかった分野なんでやってみたかったんだけどね、 トレイト+ジェネリック+静的型システム+ライフタイム辺りのコンボがどうにも辛い。
で、結構な間何も更新してなかったんだけど 今後はまた Clojure に出戻って気がついたような事を書いていきたいと思う。
Clojure も 1.8.0 になったってのを見た後くらいから全然触ってなかった。 またちょっと触りだして思ったのは cider も進化してんなあ、界隈の hot なライブラリ全然知らないなあ、 多少の起動の遅さというデメリット以上に REPL はやっぱり便利過ぎるなあ、などなど。
こんな事思ってる人いないかなあと ググッてこんな記事見つけてみたりしてまた Clojure へのモチベーションを上げてみたり。 postd.cc
ちなみにこの記事は Rust については全く触れてないので注意。
と言うわけで Rust は言語というよりは周りの開発環境的なものが もう少しこなれたらまた手を出すかも、出さないかも。 Winodows だけかもしれんけど racer もやたら固まるしなあ。
以上駄文でした。
環境変数設定画面を直接開く方法
前日本語でググって見つからず諦めてたが今日英語でググッてみたらあった。
と言うわけで以下のような感じでそのままバッチファイルにしたものを PATH 通してる所に放り込んだ。
@echo off rundll32 sysdm.cpl,EditEnvironmentVariables
コマンドプロンプト画面は出るが常時表示させておくものでもないので気にしない。
Rust Win GNU ABI で実際何か作る時は MinGW-w64 の gcc にパスを通しておくのが実質必須と言うお話
今回は Conrod と言う GUI ライブラリのサンプルを動かしてみたくて 試そうとしたら、コンパイルが途中で止まったのが発端。
確認環境
公式ガイドにあるサンプルを動かす Cargo タスクを実行すると外部 Crate のコンパイルで止まった。
cargo run --release --example text
エラーメッセージはもう長くなりすぎるんで省略するけど、
miniz-sys 0.1.7
という Crate のコンパイルで止まっていた。
現象やメッセージがどんなものかと言うのは gcc-rs の issue#7と同じなので 詳しくはそちらをどうぞ。
今回問題の原因は何かと言うと、Cargo は前の記事でもチラッと話題にしたが、
- Cargo にはカスタムビルドスクリプトと言う機能があり、
- Crate によってはそこで更に gcc-rs と言うカスタムビルドスクリプトから C/C++ コンパイラを直接呼ぶ為のライブラリが必要で、
- gcc-rs は Win GNU ABI の場合、MinGW-w64 の gcc に PATH を通している事が必須
と言う事。
ここで一つ疑問なのは Win GNU ABI 版 Rust 同梱の gcc じゃダメなのかと言う事だが *1、 rustc から呼ぶ gcc は主にコンパイル済みライブラリのリンクを目的として使ってて C ソースからコンパイルするにはヘッダファイル等も一緒に入ってないと色々と都合が悪く こういう仕様になってるみたい。Rust 同梱の gcc でも試してみたが確かに動かなかった。 まあ C ソースのコンパイルから必要な Crate だとそうするしかないか。
似たような主旨の事は実は gcc-rs の README にも書かれているんだが、この事は外部 Crate を使わずに何か作るという可能性が低いから 公式でももっと大きく書いておいて欲しいと個人的には思った。 *2
また、リンク先には MSVC 環境の場合もフォローしてるけど自分は GNU ABI でやってるので未確認。
最後に MinGW-w64 の gcc に PATH を通せば最初のコマンドは無事実行できた。
*1:実際 The Programming Language Rust でサンプルコードを試している時は今回の事はなかった→Rust 同梱/MinGW-w64 いずれの gcc にも PATH は通していなかったので
*2:未読のForeign Function Interfaceなどに書いてあったらごめんなさい
Cargo で gcc のフラグを任意に渡す方法→cargo rustc からのみ可能(2016-05-08修正)
要は Windows の GUI サンプルコードをお試しで動かすと コンソールも一緒に立ち上がってたのが嫌だったので調べると見つかった。
で、このフラグを Cargo から渡す方法はないか調べた。
確認環境
2016-05-08修正
申し訳ない、最初に書いた記事は自分の誤読で案について話しているだけで Cargoの設定ファイルで gcc へ任意のオプションを渡す方法は現時点では存在しておりませんでした。
ただ、cargo rustc
からであれば渡せます。
cargo rustc --release -- -C link_args=-Wl,--subsystem,windows
これは簡単なツール作って Windows エクスプローラから exe 起動して コマンドプロンプトが出なくなったのを確認済。
修正前の記事で紹介した
issue#544の件
のはまさしくこの cargo rustc
での機能を実装したよと言う事のみでした。すみません。
↓で見つけた。 github.com
やり方は簡単で、対象のプロジェクトの Cargo.toml に以下の一文を追加する。
[rustc] flags="-C link_args=\"-Wl,--subsystem,windows\""
MSVC ABI だとこの現象が発生しないのかは未確認。
ちなみにissue#544はcargo rustc
タスクでコマンドラインからオプションとして渡す方法がメインの話なのだが、
こちらでやると
前の記事の方法で設定した
追加ライブラリサーチパスが有効にならない。あくまでcargo run
orcargo build
した時だけって事なのかな。