-- / --
--
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

04 / 26
Sat

もう 4 月も終わりか…、 という感じの Ziphil です。

いつの間にか Java8 がリリースされていました。 どうやら、 ラムダ式が追加されたみたいで、 これは Rubyist である (だった?) 自分には嬉しい仕様です。 どんなのかというと、 こんなのです。

List list = Arrays.asList("foo", "bar", "baz", "hoge", "huga");
list.forEach((String s) -> {
  String data = "[" + s + "]: " + s.length();
  System.out.println(data); 
});

map とか filter とか reduce とかもちゃんとあります。 ただし、 この場合は一度コレクションを Stream オブジェクトに変換しないといけません。 これがちょっと面倒。

List list = Arrays.asList("foo", "bar", "baz", "hoge", "huga");
String result = list.stream().filter((String s) -> {
  return s.length() > 3;
}).map((String s) -> {
  return s.toUpperCase();
}).reduce((String s, String t) -> {
  return s + ", " + t;
}).get();
System.out.println(result);

ラムダ式では型推論が働いてくれるので、 引数の型は省略できます。 また、 ラムダ式を 1 行で書くばあいは return 文も不要です。 さらに、 引数が 1 つだけの場合は、 引数の前後のカッコも省略できます。 ・・・ということで、 上のプログラムは下のようにも書けます。

List list = Arrays.asList("foo", "bar", "baz", "hoge", "huga");
String result = list.stream().filter(s -> s.length() > 3).map(s -> s.toUpperCase()).reduce((s, t) -> s + ", " + t).get();
System.out.println(result);

Ruby を長いこと使ってきた私にとっては、 こう書けると非常に親近感がわきます。 Ruby だとこんな感じで、 似てますからね。

list = ["foo", "bar", "baz", "hoge", "huga"]
result = list.select{|s| s.length > 3}.map{|s| s.upcase}.reduce{|s, t| s + ", " + t}
puts result

さて、 この Java のラムダ式ですが、 実装はどうなっているかというと、 ただの匿名クラスです。 たとえば、 Stream#filter の引数の型は Predicate<T> です。 つまり、 Java がラムダ式を Predicate<T> を継承した匿名クラスに勝手に変換してくれるイメージです。 ちなみに、 Predicate<T> は T 型の引数をとり Boolean 型を返すラムダ式に相当します。

ということで、 今までラムダ式とかクロージャとか、 そういう類のものが使えないせいで、 for 文が嫌いな私は Ruby とか Scala とか Groovy とか Kotlin とか Fantom とか Xtend とかに逃げてましたが、 その必要もほとんどなくなりましたね。

あ、 そうそう、 ラムダ式の変換先となる関数型インターフェースには、 引数の型や返り値の型などによって Supplier とか Consumer とか Prdicater とかいろいろあるんですが、 プリミティブ型専用に IntSupplier とか IntConsumer とかがあるんですよね。 それならプリミティブ型廃止すれば良いのに!


スポンサーサイト
comment ×0
03 / 04
Tue

絵がうまく描けるようになりたい Ziphil です。

Java のアプレットなんかでよくこんなコードを見ます。 途中省略してます。

public class TestApplet extends Applet {
  public void paint(Graphics graphics) {
    graphics.drawImage(image, 10, 10, this);
  }
}

こういうものなんだなーってことで、 これまで気にしてなかったんですが、 やっぱり気になります。 この第 4 引数って何ですか。 Java の API に聞いてみると 「イメージオブザーバ」 だと言われますが、 何ですかそれ。

簡単に言ってしまえば、 イメージを観察するオブジェクトみたいです。 image observer ですから、 そりゃそうですね。 じゃ、 具体的に何をしてるんでしょうか。

まず、 getImage メソッドなどによって、 変数に Image オブジェクトが格納されたとしましょう。 この時点では、 画像はまだ読み込まれません。 次に、 drawImage メソッドが呼ばれたとしましょう。 ここで初めて、 画像本体が読み込まれ始めます。 しかし、 drawImage メソッドが呼ばれたときから読み込みが始まってしまっては、 この時点で画像を描画することはできません。 そこで、 イメージオブザーバが逐一イメージの読み込み具合を観察して、 それに応じて何度も画像を描画してくれるわけです。

「イメージオブザーバなんて考えるの面倒だから null で良いじゃん」 ということで、 null を指定してみるとどうなるでしょう。 イメージオブザーバがありませんから、 イメージの読み込み具合に応じて再描画してくれなくなります。 drawImage メソッドが呼ばれた瞬間は、 まだイメージは読み込まれ始めたばかりですから、 この時点では何も描画できず、 結果として最後まで何も描画されません。

では、 ゲームなどのループ処理によって、 何度も drawImage が呼び出される場合はどうでしょう。 1 回目のループで drawImage が呼び出された時点では、 何も描画することはできませんが、 ループで何度も drawImage が呼び出されるので、 結果としてきちんと画像が表示されます。

まあ、 ざっとですがイメージオブザーバはこんなことをする便利なやつです。


comment ×0
10 / 01
Tue

Graphics クラスの drawString メソッドって、 単に文字列を描画するだけじゃないですか。 そうすると、 例えば RPG で、 この 200px の間の中にクエスト情報なんかを描画したいってときに、 普通に描画するとはみ出ますよね。 それで、 幅が 200px になったところで自動的に改行してくれるような機能があると便利ですよね。 ということで、 そういうことをしてくれるメソッドを作りました。

private void paintFixedString(Graphics graphics, String string, Integer x, Integer y, Integer width, Integer height) {
  Integer i = 0;
  Integer cx = x;
  Integer cy = y;
  Integer length = string.length();
  String paintedString = "";
  for (; i < length; i ++) {
    Character current = string.charAt(i);
    String currentString = current.toString();
    if (currentString.matches("\\w")) {
      paintedString += currentString;
      continue;
    } else if (!paintedString.equals("")) {
      paintedString += currentString;
    }
    if (i + 1 < length) {
      Character next = string.charAt(i + 1);
      String nextString = next.toString();
      if (next.equals(',') || next.equals('.') || next.equals('、') || next.equals('。') || next.equals('」') || next.equals('』')) {
        if (paintedString.equals("")) {
          paintedString += currentString + nextString;
        } else {
          paintedString += nextString;
        }
        i ++;
      }
    }
    if (paintedString.equals("")) {
      paintedString += currentString;
    }
    Integer px = graphics.getFontMetrics().stringWidth(paintedString);
    if (cx + px > x + width && paintedString.equals(" ")) {
      paintedString = "";
      continue;
    }
    if (cx + px > x + width && (paintedString.equals("「") || paintedString.equals("『"))) {
      cx = x - px / 2;
      cy += height;
    } else if (cx + px > x + width) {
      cx = x;
      cy += height;
    }
    graphics.drawString(paintedString, cx, cy);
    cx += px;
    paintedString = "";
  }
}

引数の width に収めたい幅を入れると、その幅に収まるように自動的に改行しながら文字列を出力します。 それに加えて、 閉じカッコが行頭に来ないようにするなどの禁則処理も行ってくれます。 さらに、 英単語はちゃんと単語の途中で改行されないようにもなっています。

コードの解説はちょっと複雑なのでまた今後にしますが、 なかなか便利です。 ただ、 本当はこれを Graphics クラスに定義したかったんですよね・・・。 そうすれば、 graphics.paintFixedString( ~ ) みたいに書けるんですが。 Java は既存のクラスにメソッドを定義することができないので、 仕方ないです。


comment ×0
09 / 28
Sat

ちょっと Java を触ってたので、 いろいろメモしておきます。

まず、 真偽値 (Boolean オブジェクト) の値の反転なんですが、 最初はこんな感じで書いてました。

if (boolean) {
  boolean = false;
} else {
  boolean = true;
}

はい、 長くて鬱陶しいですね。 ということで、 良い方法はないかと調べてみたのですが、 普通に良い方法がありました。

boolean ^= true;

そんな演算子を使おうなんて思いつきませんって。 あ、 boolean = !boolean っていう記法もあるんですが、 これは変数名が長くなったとき鬱陶しいですし、 同じ変数名が 2 回も出てきて見た目が悪いので、 私は使いません。

さて、 もう 1 つなんですが、 FontMetrics クラスの getAscent メソッドが正しい値を返してくれないみたいです。 調べてみると Java 1.0 の頃からのバグみたいで、 どうやら getMaxAscent メソッドと同じ値を返しているようです。 仕方なく私のコードでは、 手動で値を加算したりして修正してますが、 あまり良い方法ではありませんね。 これは結構困ってます。 コードの美しさ (自分なりの美しさですが) にもこだわってるので、 こういうのがあると落ち着けないんですよ。


comment ×0
09 / 25
Wed

キーボードからの入力を検出しなきゃいけないなーと思って、 その方法を調べてみました。 KeyListener インターフェースを実装すれば良いみたいです。 そうすると、 キーが押されたときや離されたときに特定のメソッドが呼ばれ、 キー入力が検出できるわけです。

んー、 StarRuby では、 Input モジュールを使ってコードの好きな位置でキー入力の検出ができたんですが、 Java じゃ無理みたいです。 ということは、 キー入力の検出だけまとめなきゃいけないんですね。 全然問題はないんですが、 慣れてないだけに違和感を感じます。


comment ×0
09 / 23
Mon

Java でゲームを作ることになって、 これまで勝手にやってくれていたことを自前でやらないといけなくなりました。 例えば、 FPS の維持とか。 StarRuby では、 メソッドの引数に FPS の値を渡してやるだけでよかったんですが、 Java では全部自分でやらないといけないんですよね。

ということで、 FPS を一定に維持してくれるクラスを作ってました。 仕組みはそんなに難しくないです。

クラスのフィールドとコンストラクタはこんな風になっています。

private Integer idealFps;
private Long realFps = 0L;
private Integer idealSleepTime;
private Long oldTime = 0L;
private Long errorTime = 0L;
private Long sleepTime = 0L;
public FpsSleep(Integer fps) {
  idealFps = fps;
  idealSleepTime = 1000000 / idealFps;
  oldTime = System.currentTimeMillis();
}

idealFps は設定する FPS、 idealSleepTime は 1 フレームの待ち時間 (ミリ秒) の 1000 倍です。 固定小数点数を扱うため、 時刻や時間を扱う変数には全て 1000 倍された数値が入れられます。 errorTime は待機時間の誤差です。 これについては後で説明します。

さて、 クラスの使い方ですが、 画像の描画やオブジェクトの更新を行う前に、 以下の start メソッドを呼び出します。

public void start() {
  oldTime = System.currentTimeMillis();
}

処理前の時刻を oldTime に保存してるだけです。 そして、 1 フレーム分の処理が終わったら、 以下の finish メソッドを呼び出します。

public void finish() {
  Long newTime = System.currentTimeMillis();
  sleepTime = (idealSleepTime + errorTime - (newTime - oldTime) * 1000) / 1000 * 1000;
  if (sleepTime < 1000) {
    sleepTime = 1000L;
  }
  errorTime = (idealSleepTime + errorTime - (newTime - oldTime) * 1000) - sleepTime;
  if (errorTime < 0L) {
    errorTime = 0L;
  }
}

まず、 現在時刻を newTime に代入します。 次に、 待機すべき時間を sleepTime に代入しています。 処理にかかった時間は newTime - oldTime で得られるので、 この分を 1 フレームの待ち時間である idealSleepTime からひいてやれば、 待機すべき時間が求まります。 最後の / 1000 * 1000 というのは、 百の位以下を切り捨てて 1000 の倍数にしているだけです。 実際に待機するミリ秒数は整数ですから、 1000 の倍数にしておかないといけませんからね。

さて、 では errorTime は何なのかというと、 1000 の倍数に切り捨てられた誤差が格納されています。 例えば FPS が 60 に設定されているとすると、 処理と待機を合わせて 1 フレームは 16.666 ミリ秒になります。 しかし、 実際に待つのは 16 ミリ秒なので、 1 フレームごとに 0.666 ミリ秒の誤差が生まれます。 この誤差によって、 FPS が正確に 60 に保たれなくなってしまうわけです。 そこで、 誤差の 0.666 ミリ秒を errorTime に保存しておいて、 次のフレームの待ち時間を計算するときに、 1 フレーム分を 16.666 ミリ秒ではなく、 誤差をたし合わせた 17.332 ミリ秒として計算するわけです。 すると、 ここでは 1 フレームは 17 ミリ秒となり、 誤差 0.332 ミリ秒がまた次のフレームに持ち越されます。

さて、 これで理論上はうまくいくはずなんですけどね、 FPS を 60 に設定して動かしてやって、 実際の FPS を計測すると 57 くらいで安定するんですよね。 何でですかね。 ちょっとよく分からないんですよね・・・。


comment ×0
back-to-top
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。