Vim Advent Calendar 2012 の254日目の記事です。昨日の記事はcohamaさんで、明日の記事はmanga_osyoさんの予定です。
さて、前回
今日は8月6日ですね。さて問題です。「2013年8月6日からVim Advent Calendar 2012とVim Advent Calendar 2013が並走開始する日まで何日か」をVim scriptで計算してください。
大前提として、Vim Advent Calendar 2013は2013年12月1日に確実に開始するものとします。また、Vim Advent Calendar 2012は、http://atnd.org/events/33746 にかかれているように
> 通常の Advent Calendar とは違い、特に終了期日は設けないのでどんどん参加しましょう!
とあるので、当然2013年12月1日よりもあとも無限に継続されます。
と書きました。この問題に実際にとりくんだ人がどのくらいいるのか、著者は気になっています。
さて、今回からVimでMinecraftの開発を行うための記事を3つに分割して記述していきます。
Vim使いの多くがMinecraftをプレイしていることが観測されますが、まだMinecraftについてよくわからない人のために簡単に解説しましょう。
http://ja.wikipedia.org/wiki/Minecraft
Minecraftは概念です。Minecraftをプレイしたことのない人は、Minecraftを説明する文書を読んでも理解できないでしょう。Minecraftとは何か、それを理解するためにはMinecraftをプレイする必要があります。他人がプレイしているのを観るのと、自分がプレイするのも、全く別物です。そういう意味で、MinecraftとVimは本質的に同じです。
さてMinecraftとは何でしょうか。これはメタゲームです。つまり、Minecraftを用いてゲームを作ることができる、その土台となっているものです。興味深いことに、Minecraftはメタゲームであり、なおかつそれ自体がゲームにもなっていることです。多重構造に3D酔いするかもしれませんが、練習すればなれます。
Vimがメタエディタでり、なおかつそのものがエディタとして使えるということの類似性から、MinecraftとVimはこの点においても本質的に同じといえるでしょう。
Minecraftはclient-server型モデルの系です。MinecraftをAさんとBさんとCさんがプレイするためには、たとえばAさんがserverを起動し、AさんとBさんとCさんそれぞれがclientを起動した上でそのサーバに接続します。serverは世界全体のデータの保存や、世界のルールの処理をします。clientはプレイヤの操作を受け付け、また描画を担当します。MVCモデルにおけるMをserverが行いVCをclientが行なっているととらえることもできるでしょう。
MinecraftそのものはJavaで記述されており、server, clientともにソースコードは公開されておらず、コンパイル後のjarファイルが提供されています。
興味深いことに、serverに関する機能をpluginで一部書き換えることができます。このためのAPIは一般公開されており、server側がアップデートされても安定して利用することができます。
また、client側に関する機能も有志による分解・解析が行われており、それにたいするAPIも提供されています。server側ほどではありませんが、そこそこ安定して利用することができます。
このうち著者がとくに重要だと思うのが前者、serverに関する挙動の書き換えです。MVCのうちMが最も重要である、つまりロジックの組み換えが最も柔軟性が高いと考えるからです。なお、このserverそのものの実装はCraftBukkitと呼ばれており、そのAPI (JVM用のInterfaceのあつまり) はBukkitと呼ばれています。
Bukkit APIを用いたMinecraft開発はJVM上で動作する任意のプログラミング言語が利用可能です。Javaという言語も利用可能ですが、非常に冗長な記述、低い柔軟性・表現力などから、実用性はないでしょう。一方ClojureやJRubyを用いれば、保守性の高いコードが得られます。
著者の経験上、静的型付けの言語よりも動的型付けの言語の方がうまくいくようです。著者は仕事でScalaをフルタイムで使っているため、Scalaについては最低限以上の知見をもっているはずなのですが、Minecraft開発においては、Clojure, JRubyとともに実際の経験上不便に感じることが多くありました。特に強い選好がなければ、ClojureやJRubyを用いるのがよいでしょう。
著者はまだGroovy, Jython, Rhinoなどで実験していませんが、そのあたりに詳しい方は、ぜひ挑戦してみてはいかがてしょうか。
さて、ここまで言葉による説明が続きました。ちょっぴり具体例を出してみましょう。
Minecraftの世界には羊がいます。羊毛というアイテムがあります。また、「殴る」という概念があります。さて、「(プレイヤが)羊を殴るとその羊が羊毛を落とす」というのを実現するにはどうすればよいでしょうか。
細かく分割しましょう。殴る、その受動態である殴られるというのは、多々ある「ダメージを受ける」の一つです。また、「羊が羊毛を落とす」というのは「羊の位置に羊毛というアイテムが発生する」といいかえることもできます。以上のことをまとめると、Clojureではいかのように記述します。
(defn on-entity-damage-by-entity [evt]
(let [defender (.getEntity evt)
damager (.getDamager evt)]
(when (and (instance? Player damager) (instance? Sheep defender))
(let [location (.getLocation defender)
world (.getWorld location)]
(.dropItemNaturally world location (ItemStack. Material/WOOL 1))))))
詳しく解説しましょう。
まず、defn
, let
, when
, instance?
, and
はclojureそのものの機能です。それ以外はBukkit経由のものです。
on-entity-damage-by-entity
、つまり「エンティティ(生物など)がエンティティ(同じまたは別の生物)によってダメージを与えられる」というイベントが発生したときにどのような処理を行うか、という記述をしようとしていることが読み取れます。このイベントに関する処理を持ったオブジェクトをevt
という名前で引数として受け取っています。
なお、このevt
はJVM上ではEntityDamageByEntityEventというclassです。getEntity()
, getDamager()
などのメソッドをもっています。
そして後述の処理で、そのイベントをもとにいろいろやって最終的に羊毛を発生させています。
Rubyに詳しい方のため、JRubyを用いた例も記述しましょう。
def on_entity_damage_by_entity(evt)
defender = evt.getEntity
damager = evt.getDamager
if Player === damager && Sheep === defender
location = defender.getLocation
world = location.getWorld
world.dropItemNaturally(location, ItemStack.new(Material::WOOL, 1))
end
end
ほぼ直訳しました。
なお、clojure, jrubyともに説明のため若干冗長に記述しました。実際に記述するなら、以下なようなテクニックを用います。
- clojure
when
ではなくcondp instance?
を用いて、より宣言的に記述する
- jruby
dropItemNaturally()
ではなくdrop_item_naturally()
などjruby用に変換された名前で呼び出す
今回は導入部分について解説しました。次回はいよいよ、実装上の詳細、そしてVimとの連携について詳しく解説します。