NanoGLを試す

この記事はRuby Game Developing Advent Calendar 2016 - Adventar20日目です。昨日は空いてました。

NanoGL

NanoGLをGoogleさんに問い合わせるとWebGLラッパーが出てくるが、ここで言うNanoGLはそれではなく、nanovgを描画のベースにしたマルチプラットフォームの2DゲームライブラリNanoGLのことである。基本C用のライブラリだがCRuby用/mruby用のバインダも用意されている。使い込んでいるわけではないので「こういうものがあるよ」という紹介レベルで。

できること

描画がnanovgベースなのでHTML5canvasっぽいAPIでベクタグラフィックスができる。nanovgのページの下のほうにサンプルのスクリーンショットがあるが、あんな感じの描画ができる。NanoGLの描画はnanovgの関数ほぼそのままなので、nanovgのサンプルを部分的に移植してみたところ、

という感じで同じようなものを描画することができる(コード)。まあ、ベクタグラフィックスのコードが面倒な感じになるのはしょうがないのかな。
他、GLFW3でのキーやマウス、パッドの入力、OpenAL-Softによる音の再生など基本的な機能が用意されている。

ポイント

オープンソースマルチプラットフォームライブラリを使っているのでNanoGLもマルチプラットフォームである。WindowsMacでのビルド方法が解説されている。ubuntuディレクトリがあるしubuntu対応とも書いてあるのでLinuxでもビルドできるかもしれない。また、Windows用にバイナリでもリリースされていて、gemをダウンロードしてインストールするとバイナリが入る。やさしい。また、mrubyはNanoGLが組み込まれたexeが同梱されているので、動かしてみたい人はこれで。
サンプルはC用とRuby用がそれぞれ14個ずつ用意されている。実際に遊べるSTGなどもあるので参考になる。どうでもいいが04-Image.cが03-TransformMulti.cと同じ中身になってしまっている。

Ruby用ラッパーはほぼCインターフェイスをそのままffiで呼ぶ感じなので、基本的には何かしら自分で書き足して使う感じになるだろうと思う。nanovg自体がそんな感じだし。
あと、Ruby用ラッパーのVideo.rbに少々バグがあって上記のnanovgサンプルの一部を移植したやつはこの記事を書いてる現時点の最新バイナリリリースでは動作しない。リポジトリにはバグ修正がコミットされているのでそれを落として上書きすれば動くようになる。
ちなみにリファレンスは無いがRuby用ラッパーにはコメントがバッチリ入っているのでそれが参考になる(Video.rbだけ)。

dxruby on nanogl

そもそも存在を知ったきっかけがコレ。DXRuby互換ラッパーが作られている。現状ではDXRubyのclassic_sampleが動作するそうだ。手元でいくつか動かした感じではちゃんと動いていた。SoundEffectも(?!)。完全互換は原理的に難しいとして、どこまで行けるものなんだろうね。
ともあれ、ある程度の互換性さえ取れれば例えばRuby教室などの教材はそのままにMacLinux対応できて、それならWindows用もコレでいいんじゃね?ってなることが期待できるので、作者の人にはぜひとも頑張って頂きたい。

おしまい

サンプルは地味だがnanovgの表現力は非常に高いので、手軽にベクタグラフィックスで遊んでみたい人にはオススメ。
明日の記事は・・・こっちも空いてますな。

アクティベーションコンテキストについて

RubyでFiddleを使ってWindowsAPIを叩けばWindowsアプリを作ることが、まあ、できるというのは検索するとたくさん出てくるのでわりとメジャーな話。しかし普通に作るとボタンなどの見た目がクラシックになってしまう。こんな感じ。

Windows7なら7的な見た目になってほしい。こんな感じに。

この見た目はcomctl32.dllのバージョンに依存する。バージョン6を使うようにすれば見た目が変わるのだが、何もしないとバージョン5が使われる。comctl32.dllは共有side-by-sideアセンブリとしてインストールされていて、バージョン6を使うためにはマニフェストファイルを作ってexeに埋め込む必要がある。が、こちとらRubyでウィンドウを動的に作っているわけで、そんなもんどうやって埋め込むんじゃ、となるわけだ。そこで登場するのがアクティベーションコンテキスト。
WindowsAPIのCreateActCtxでアクティベーションコンテキストを作ってマニフェストファイルを読み込み、ActivateActCtxでアクティブ化してやると、それ以後に生成したコントロールの見た目が変わる。非常にマイナーなAPIで調べるのにちょと苦労したが、確かにできるっぽいのでメモ代わりに記事にしておいた。ソースはこちら
ポイントはマニフェストファイルは何故かどうしてもファイルで指定する必要があるっぽく、テンポラリに吐き出して読むようにしたところ。なんでメモリ内のデータじゃダメなんすかね。

Rubyの標準添付ライブラリFiddleでゲームプログラミングする

この記事はRuby Game Developing Advent Calendar 2016 - Adventar の12日目です。11日目は土屋つかささんのrubyゲーム開発にユニットテスト/テスティングフレームワークを導入する(2)【実践編】 - 土屋つかさの技術ブログは今か無しかでした。Ruby...ゲーム...ユニットテスト...うっ、頭が...
まあ、ゲーム関連でのユニットテストは難しい問題で、スピーカから出てくる音をマイクで拾って判定するわけにもいかないので、とりあえず全ての機能をテストすることができないのは明らかである。っていうかそもそも外部機器を扱わない部分を考えても、例えばDXRubyのSpriteで言うところのSprite#x=で設定した値がSprite#xで取得できることはテストできても、それがSprite#yで取得する値に影響を与えていないこと、などをテストし始めると現実的には不可能な量になってしまうので、つまりは全てのユニットテストにおいて取れる戦略はたった一つ、「できるテストをやる」。ああ、耳が痛い。
ちなみに高尾さんのSDLを使ったDXRuby互換ライブラリdxruby_sdlではRSpecを使ってテストを書いていて参考になる。ここではSoundは音声ファイルを読み込んで再生して例外が出ないこと、をテストしている。なるほど。

さて、今回はRubyの標準添付ライブラリFiddleでゲームプログラミングということで、WindowsDirectXを叩く方法を紹介する。DirectXはCOMインターフェイスなのでRubyからは普通の方法では呼べないが、Cインターフェイスが存在するのでそれに沿った扱い方で呼ぶことが可能である。

続きを読む

連鎖性言語を作る11

原因がまったく想像できない難解なバグに遭遇した場合、怪しいところから調べるのではなく、当たり前に大丈夫なはずのところから本当に大丈夫であることを確定していくほうが結果的に速く原因に辿り着けることがある。急がば回れという話だが、真剣におかしいところが不明な時はどこか見落としがあるはずで、見落とすということはそれはつまり大丈夫だと思い込んでいる場所なわけなので、このアプローチはそれなりに正しい。
という前振りとはぜんぜん関係ないのだが、言語を(というか実際にやっているのは言語処理系を)作るのもやったことないことの塊なので、地道にひとつひとつ積み上げて、それが可能なこと、それから問題が発生するなら解決法の模索を、踏みしめるようにじっくりと進めることが大切である。言語処理系開発の疑似体験とでも言おうか。この経験が今後何かに生きるとも思えないし、このシリーズの記事が誰かの役に立つともイマイチ思えないのだが、まあ、これが趣味なので。
ということで今回は地味に半歩ほど進める感じで、Rubyからオレ言語を呼び出すインターフェイスを考える。

続きを読む

連鎖性言語を作る10

オレ言語のコードをRubyコードに変換してevalすれば、VMで実行するのと比べてディスパッチが無くなって速くなる。毎回evalするとRubyコンパイルが発生してオーバーヘッドがあるので、evalでProcを生成して、実行時はそれを呼ぶだけ、という形にしておくのが望ましい。これはテキストを編集してうんぬん、というだけなので簡単であり省略。
新たなチャレンジとして、オレ言語→Rubyトランスレータなどがよいかもしれない。変換したコードをrbファイルで出力しておいて、それをrequireして呼び出すことで実行できる、みたいな。Rubyを呼び出すことはできていたが、Rubyから呼ぶインターフェイスは今まで無かったのでそのへんを構築するという課題も解消する。んじゃこれいってみよう。
というところまで考えて、ふと思った。
Rubyコードに変換してevalするだけってのは、本当に簡単なのか?印象としては簡単っぽいけども、やったことがあればまだしも、やったことの無いことに対して簡単なはずだから省略ってのはちょっとアレなのではないか。簡単ならすぐできるわけだし、とりあえずやっておけばいいんじゃないか。と。
やってみよう。

続きを読む

連鎖性言語を作る9

VMの命令列にコンパイルして実行するようになったのでなんとなくVMっぽい気がする感じになったところで、命令が2個しかないのは物足りない。これを増やすことを考えてみよう。オレ言語はスタック指向であり、スタックマシンのVMとは相性がよい。相性がよいと言うか、言語がシンプルなせいでほぼそのままストレートに組み込みワードをVM命令化することができる。
実際のところ、コードジェネレータをこのようにして、

module CodeGenerator
  def self.generate(ast)
    ast.instance_variable_set(:@bytecode, ast.map{|d|
      case d
      when :swap
        Instruction.new(:swap, nil)
      when :drop
        Instruction.new(:drop, nil)
      when :dup
        Instruction.new(:dup, nil)
      when Symbol
        Instruction.new(:call, d)
      when OreSymbol
        Instruction.new(:push, d.sym)
      else
        Instruction.new(:push, d)
      end
    })
  end
end

VMをこのようにすれば

  def run(ast)
    unless ast.instance_variable_defined?(:@bytecode)
      CodeGenerator.generate(ast)
    end

    ast.instance_variable_get(:@bytecode).each do |d|
      case d.insn
      when :swap
        @stack[-2], @stack[-1] = @stack[-1], @stack[-2]
      when :drop
        @stack.pop
      when :dup
        @stack << @stack[-1]
      when :call
        @words[d.data].call
      when :push
        @stack << d.data
      else
        raise
      end
      break if @break_flg
    end
  end

swap、dropdupの3つの組み込みワードはVM命令化できる。じゃあこの調子で全部VM命令化してしまえばいいのかと言うとそうでもない。VMとかの専門家ではないのでイマイチだがちょっと考えてみる。

続きを読む

連鎖性言語を作る8

オレ言語の実装として、Rubyの配列にオブジェクトを詰めたものをASTとして扱っているのは特徴的だ。実際コード自身がオブジェクトを並べたような見た目をしているのでこれはこれで直感的とも言える。ところでVMはそのASTを解釈して実行しているわけなので、これは名前はVMだが素朴なインタプリタじゃないのか、という思いがある。今回はここを名実ともにVMにしてみよう。
ASTをもっとASTっぽくNodeなんちゃら的なクラスのインスタンスを詰めた配列にしてもいいのだが、それは単純に無駄っぽいので、ここはそのままにしておく。ASTをなんらかの命令列に変換して、VMでの実行直前にコンパイルVMコンパイル後の命令列を実行する。と言うとちょっとカッコイイのだが。

続きを読む