※一覧:
その1→http://qiita.com/YSRKEN/items/ac75ffcb0f72adbd2a7f
その2→ここ
その3→http://qiita.com/YSRKEN/items/ceb75d86650b030c4044
#概要
前回の投稿でとりあえずリリースした私でしたが、まだまだ不満点は多いと感じました。まあ当然ですよね、Javaについては始めたばかりでしたし。
というわけで、勉強しつつ種々の改良を加えながら開発を進めていきました。
※本文中にはブラウザゲーム「艦隊これくしょん」のスクショの画像が説明として出てきますが、これらの著作権を次の通り示します。
艦隊これくしょんは角川ゲームズ/DMM.comが製作、運営しているオンラインゲームです。
(C) 2015 DMM.com POWERCHORD STUDIO / C2 / KADOKAWA All Rights Reserved.
#さてさて、分割いたしましょう!
前回リリースしたコードはnano.javaの1つだけでしたが、Javaでは1ファイルにつき1クラスが推奨されています。Javaではクラスごとにmain関数を定義できるので、分割してコードを書いてもデバッグしやすい(main関数を消さなくてもそのまま繋げて実行できる)のがその主な理由だと思われます。コンパイル時に.classファイルが元のソースコードの数に関係なくクラスごとに生成されたり、実行時やMANIFEST.MF作成時に**「クラス名」を指定するのも関係しているでしょう。というわけでコードを分割しました。
もっともコードを分割するなんて他の言語でもよくやること**ではありますね。前回の記事で取り上げた「艦これ一覧めいかー」は約2900行だそうですし、「おりこうさんな秘書」だと分割されたものを全て合わせると約4400行、データファイルを含めると4800行強でした(公開されているソースコードをカウントした)。なおHSPだと#includeで繋ぎ合せることはできますが、Javaのimportのように器用な運用はできませんね……。
#継承はもちろん、抽象クラスだよ! ほら、つるつるして機能的なんだって
前回書いていて気付きましたが、unit_windowとsort_windowってやってることほぼ一緒では?
というわけで、両者の共通点を括りだした抽象クラスとしてjoin_windowを用意。打鍵量を減らして楽になりましたとさ。おしまい。
それで済むならこんな記事書かないんだよ!
それで済むならこんな記事書かないんだよ!!
御存知の通り、abstractに宣言はstatic宣言とかと重複できません。また、メンバ変数は継承できないので、定数なんかをクラス内で宣言する場合は継承元と継承先のどちら側に実装するかを選ぶ必要があります。で、私はstatic finalにしたくて後者を選んだので、アクセッサを大量に記述する羽目になったんですよこれが。
もっとも打鍵量は間違いなく減りましたし、機能追加しても二度手間にならず相当楽できたのは認めますが、初見殺しなのは否めないんだよなぁ……。
#Javaのrepaint攻撃、行きますなのね!
join_windowクラスで定期的に画面を再描画するため、クラス内で「JPanelを継承したjoin_panel」クラスを定義して、その中のpaintComponentメソッドで画面を再描画させることにしました。ここまでは普通ですよね?
ただ、前回はpaintComponent内で「保存用バッファ(画像を等倍で繋ぎあわせたもの)を縮小して描画+枠線描画」だったのですが、今回は保存時に複数表示形式を選べるようにするため、サブバッファをグリッドの数だけ用意してそれぞれにスクショを記憶し、paintComponentではそのグリッド数分だけ縮小処理を掛けて描画+枠線描画としました。複数の形式がどんなのかは、この記事の末尾にある「おまけ」の項目をご覧ください。
こうして実装すると……まあ確かに動きますが、CPUを独占する勢いで重いんですよねこれ。幸いマルチコアCPUだったので1コア分(2コアなら50%)しか消費しませんが、どう考えても不具合です。本当にありがとうございました。
ということで、表示用バッファを別に用意して、paintComponentではそれをそのまま表示する+枠線描画するように書き換えました。これで処理もグッと軽くなって一安心。もっとも、頼んでもいないのに(画面が別に隠れているわけでもないのに)毎秒10回以上paintComponentが呼ばれるのはJavaの仕様なのかクソ仕様なのかは永遠に謎ですが。
#そうね、参照は重要だと思います
もはや常識に近いですが、Javaではプリミティブ型以外は=演算子しても参照しかコピーされません。なので、例えばBufferdImageの実体をコピーしたい場合は、newして=演算子で書き込むかdrawImageで書き込むか……ということになります。
今回の実装では、前回と異なり、表示用バッファと保存用バッファが別なので、こういったコピー処理とかも2度手間が掛かります。その度に毎回drarImageしてたのでクソ面倒でした。しかも、調べるとJavaのクラスはみんなclone()というメソッドが定義されていたのですが、再実装しないと使えない上、どうせ参照しか返さないので結局は今回の目的からするとゴミだということが判明して、結局ディープコピーするメソッドを実装しなおしました(参考記事)。やめろよ……コピーコストが嫌なのは分かるがもはや嫌がらせにしかなってないだろ……。
#ウィンドウにどぼーん、してきます!
今や当たり前となったドラッグ&ドロップ機能ですが、Javaでももちろん利用可能です。
具体的には詳しい説明記事があるのでそれを読めばいいのですが、要するにこういうことです。
- まずsetTransferHandler(new DropFileHandler());として、ドロップを設定したいオブジェクトに対してTransferHandlerメソッドを設定する。なお、ここで言うDropFileHandlerは「TransferHandlerを継承した独自クラス」であり、適宜必要な機能をオーバーライドするようにする。
- 次にcanImportをオーバーライドして、どういったものがドロップ出来るかを設定する
- 最後にimportDataをオーバーライドして、ドロップされたものに対しての処理を行う
……で、最終的にはこれだけ書けば、**「ドラッグしたファイルについて、そのファイル名を一通り取得して表示するコード」**になります。後は受け取ったファイル名について、1つづつBufferedImageに読み込んでから画像追加用の関数に投げればいいというわけですね。
class DropFileHandler extends TransferHandler{
@Override
public boolean canImport(TransferSupport support){
// ドロップされていない場合は受け取らない
if(!support.isDrop()) return false;
// ドロップされたものがファイルではない場合は受け取らない
if(!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) return false;
return true;
}
@Override
public boolean importData(TransferSupport support){
// 受け取っていいものか確認する
if(!canImport(support)) return false;
// ドロップ処理
Transferable transferable = support.getTransferable();
try{
// ファイルを受け取る
List<File> files = (List<File>)transferable.getTransferData(DataFlavor.javaFileListFlavor);
// 順番に読み込んで追加する
for(File file : files){
System.out.println(file.getName());
}
}
catch(Exception error){
error.printStackTrace();
}
return true;
}
}
#ショートカットキー?そんなの……欲しいけど……
普通なら**「キーをフックしてAlt+Zなら画像追加処理を行う」といったような処理にするはずですが、Javaの場合はキーボードニーモニックと言って、特定キーを押すとボタンが押されたのと同じ扱いにする機能がありますのでそれを使用することにしました。具体的には、Alt+Zキーを押せば「画像追加」ボタンを押したのと同様の処理になります。
ただ、この機能だと、そのボタンと同じウィンドウがアクティブになってないとショートカットが効きません。例えば改装一覧を作成する場合は、マウスを艦これの画面とソフトの画面に交互に動かしてクリックする必要があります**。あまり美味しくない……。一応外部ライブラリを使えば実現可能なようですが、実装するかは後で考えます。
#こまめな改良が勝利をもたらすの.jar
以上までを実装したものをリリースしました。ご確認ください。
https://github.com/YSRKEN/cap_nano/releases
#おまけ