#初めに
この記事は、Life is Tech ! Members Advent Calendar 2016の17日目の記事です。
ここでは、KotlinでModdingを行う際に必須クラスの文法事項を紹介します。
予告してたのと違うとか言ってはいけない。
そのため、Kotlinの文法事項のうち、基本的なものでも一部漏れがあることがあります。
ですが、これさえ覚えておけばModdingにおいてはとりあえず最低限は困りません。
文法について詳しく知りたいとき、環境構築の方法などは下記の参考リンクを利用してください。
また、KotlinでModdingしようかどうか迷っている人も、この記事のサンプルコードコードを見て大体の感じをわかっていただけると幸いです。
Kotlinを使ってみたいな、と思ったら是非とも使ってみてください。
なお、サンプルコードは基本的に実際のModのコードから引っ張ってきて改変しています。
なのでアノテーションが付いていたり変数名がそれっぽかったりします。
###参考リンク
KotlinでModdingをする際の環境構築の方法
KotlinでMinecraftのModdingをするための環境構築 by 私
KotlinでのModdingの軽い例やTips、Kotlinについての軽い説明
KotlinでMinecraft Modding by @ralph さん
Kotlinでの文法まとめ(日本語)
30分で覚えるKotlin文法 by @k5n さん
Kotlin公式リファレンス(英語)
Reference - Kotlin Programming Language by Jetbrains
#基本文法@Modding
###超基本事項
Kotlinでは、基本的にセミコロン;が必要ありません
また、Kotlinではスコープを明記しない場合、全てpublicになります
変数においては、型推論が存在するため、型を明確に定義する必要がありません。
KotlinはNull-Safetyな言語のため、通常の型の変数にはnullを代入することができません。
nullを代入するためには、Null許容型にする必要があります。
JavaにおけるvoidはUnit、ObjectはAnyです。
###実際に書いてみる
まず、メインクラスを書いてみましょう。
なお、以下のサンプルコードはすべて1.7.10を想定しています。
//パッケージ Javaと同じ
package com.example
//インポート Javaと同じ
import cpw.mods.fml.common.Mod
//以下import省略
/*コメントもJavaと同じ*/
//class
//アノテーション部分は、Javaと基本的に同じ
@Mod(modid = "exampleMod")
class ExampleMod {
//Companion Object Javaにおけるstaticに相当する。
companion object{
//Read-Only(不可変)な変数
val Mod_ID = "examplemod"
//Mutable(可変)な変数
var Mod_Name = "ExampleMod"
//Compile-Time-Constな変数
const val Version = "1.0.0"
//型を指定して変数を宣言
val dependencies :String = "required-after:YukariLib@[1.0.0,)"
//NullAbleな変数
@Mod.Metadata
var meta :ModMetadata? = null
//スコープはvar/valの前に
private var hardRecipe = false
//返り値を持つ、一行の関数の場合{}で囲む必要がない。
//また、この場合型推論が行われる
fun isHardRecipe() = hardRecipe
}
//ローカル変数の定義
val hoge = "Hello,Kotlin!"
//関数の定義
//返り値がUnit(void)のときは返り値の型指定を省略可能。
//引数は 変数名:型名 型名必須。
@Mod.EventHandler
fun preinit(event : FMLPreInitializationEvent){
//関数のコール
loadMeta()
}
fun loadMeta(){
//キャストは、 変数名 as キャスト後の型
//また、型チェック後はスマートキャストにより、チェック後の型の変数として扱うことができる。
//例えば、NullAbleな変数でnullチェックをした場合、NonNull型に自動でキャストされる
val meta = meta as ModMetadata
//同じクラス内なら、companion objectも変数名のみで呼び出せる。
//違うクラスからなら、Javaのstaticと同様にクラス名.変数名
meta.modId = Domain
meta.name = ModID
meta.version = Version
meta.authorList.add("C6H2Cl2")
meta.description = "Add some tools better than vanilla."
meta.autogenerated = false
}
}
注意しなければいけないのは、クラスのアノテーション部分にクラス内の通常のcompanion objectの変数を代入することができないということです。以下のようなコードを見てください。
//コンパイルエラー!!
@Mod(modid = ExampleMod.Mod_ID){
companion object{
val Mod_ID = "ExampleMod"
}
}
次のように、Compile-Time-Constにすると参照できます。
//コンパイル成功
@Mod(modid = ExampleMod.Mod_ID){
companion object{
const val Mod_ID = "ExampleMod"
}
}
次に、アイテムやブロック、レシピ等の登録や変数の保持等を行うRegistryクラスを作っておきましょう。
これは、メインクラスと分けたほうが可読性が上がりやすいです。
また、全てcompanion objectに入れてしまうと大変見にくいコードになってしまいますが、クラス内のメンバーをすべてstaticにすることができる方法があります。
//package略
//import省略
//classではなく、objectにした場合、そのクラスはシングルトン(インスタンスが一つしか作成されない)になり、
//メンバーはcompanion objectと同じようにクラス名.変数名でアクセスできる
//class同様、メンバーは基本的にpublic
object ExampleModRegistry{
//クリエタブ
//抽象クラスをインスタンス化する場合、object:型名(引数){オーバーライド}の形式で行う
val tabExample = object :CreativeTabs("exampleMod"){
//関数をオーバーライドするときは、override修飾子をつける。
@Contract(pure = true)
override fun getTabIconItem(): Item {
return Items.iron_axe
}
}
//Kotlinにおいて、new句は必要ない。 クラス名()でインスタンスの作成
val exampleItem = Item()
//arrayOf()関数や、mutableArrayOf()関数で配列を簡単に作れる
//型推論により、型が自動で決定される。この場合、Block?
val exampleBlocks = arrayOf(Block(Material.rock),Block(Material.wood),null)
fun preInit(event:FMLPreInitializationEvent){
//関数内から変数を参照及び外部のstatic関数呼び出し
Gameregistry.registerItem(exampleItem,"exampleItem")
//foreach。ArrayやListなどのコレクションのすべての要素で処理を実行
//書き方は、for(変数名 in コレクション名) なお、変数は型推論
for(block in exampleBlocks){
//型判定は、isと!isで行う。また、is判定を行うとスマートキャストが行われる。細かい条件は公式リファレンス参照。
//また、!is nullや、!=null判定で、NonNull型へのスマートキャストが行われる。条件は同様。
//論理演算系はJavaと一緒
if(block !is null && block != null){
//set変数名(),get変数名()形式のゲッター・セッターならその変数名でアクセスできる
GameRegistry.registerBlock(block,block.unlocalizedName)
}
}
}
}
制御構文の類は、Javaや他の言語と大した違いはありません。
名前が違う程度です。
あと、KotlinにはJava式のfor(;;)型ループは存在しません。
Kotlinで数字のループを行うのなら、for(i in 0..334)の様に、(変数名 in 開始..終了)の形式で書きます。
こうすれば、Javaのfor(int i=0;i<=334;++i)と同じ意味になります。
次に、斧のクラスを継承して作った独自の斧を作ってみましょう。
//package略
//import略
//引数は、クラス名に()を付けてその中に。形式は関数と一緒。
//他のクラスを継承する場合、クラス名():親クラス()という形式で書きます。
//関数の宣言と同じ形式です。違いはfunかclassかだけです。
//また、親クラスに()を付けて引数を入れることで、コンストラクタでsuper()を呼ぶのと同等の処理を行います。
class ToolAxe(material:Item.ToolMaterial,name:String) :ItemAxe(material){
//Kotlinにおけるクラスのプライマリコンストラクタはinit句です。引数はクラス部分に記述するのでありません。
init{
//クラスの引数は、そのままinit句内で使用可。
unlocalizedName = "example" + name + "Axe"
//""内において、${変数}の形式で書くことで、文字列に変数を挿入可能。
setTextureName("${ExampleMod.Mod_ID}:example${name}Axe")
//objectクラスの変数の参照
creativeTab = ExampleModRegistry.tabExample
}
}
Kotlinにおいて、クラスのコンストラクタ等は、関数とほとんど同じ様に書くことができます。
new句が無いと関数かインスタンス化かがわかりにくいと言われることもありますが、クラスと関数の違いを意識しなくてもいいというのは楽だという一面もあります。
その辺は正直言って好みに別れますね。
new句があったほうが可読性が上がることがあるのも確かです。
#終わりに
どうだったでしょうか?
Kotlinにおけるmoddingの雰囲気くらいはつかめたと思います。
実際に、単純なツールを追加するだけのModなら、ほとんどこれでかけてしまいます。
まぁレシピ登録を自動化しようとして制御構文あたりでKotlin特有の仕様を使うくらいですね。
その辺の知ってたら便利系はまた別のところで紹介しようと思います。
みなさんが快適なKotlinModdingライフを送ってくれると嬉しいです。
Life is Tech ! Members Advent Calendar 2016、明日は @shrry2 さんが何か書くそうです。
もうテーマは決まっているのでしょうか? 楽しみにしてます。