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

10 / 31
Thu

昨日作った A* アルゴリズムが重いとか言いましたが、 ちょっと手を加えただけでかなり軽くなりました。

まず、 プログラムのミスで、 通れないところまで通れるものとして計算していました。 これを修正したことによって、 経路探索量が減って、 軽くなりました。 こういうエラーにならないミスが怖いんですよね・・・。

それと、 ノードリストからスコアが最も低いノードを取り出す処理を改良しました。 昨日のプログラムは、 処理を書くのが面倒だったので、 こんな感じで適当に書いてありました。

fun shortest(): StarNode {
  return _nodes.sort{it.getScore()}.first()
}

これはひどい。 スコア最小のノードを取得するだけでいいのに、 配列をソートしちゃってます。 ソートは最悪の場合 O(n2) のオーダーですから、 重いに決まってますね。 平均的には O(n ln n)) でしたっけ?

ということで、 きちんとコードを書きました。

fun shortest(): StarNode {
  var shortest: Int = 2147483647
  var shortestIndex: Int = 0
  _nodes.forEachWithIndex() {(i: Int, node: StarNode): Unit ->
    val score: Int = node.getScore()
    val status: Boolean = node.getStatus()
    if (score < shortest && status) {
      shortest = score
      shortestIndex = i
    }
  }
  return _nodes[shortestIndex]

これなら O(n) のオーダーです。

直したのはこの 2 点だけですが、 これでかなり速くなりました。 計測したわけではないので詳しくは分かりませんが、 半分くらいの時間で計算できるようになった気がします。


スポンサーサイト
comment ×0
10 / 30
Wed

仲間キャラがプレイヤーの方に近づいてくる処理について考えてました。 一番簡単なのは、 プレイヤーがその仲間キャラの右にいれば右に移動する、 みたいにする処理ですが、 これでは途中に壁があった場合困ります。 壁を回り込めばいいものを、 壁に向かってどうしようもできなくなる見方キャラとか、 何か嫌ですよね。 ということで、 うまく壁を回り込みつつ、 最短距離でプレイヤーに近づく経路を導き出すような、 良いアルゴリズムがないか探してみたところ、 「A* アルゴリズム」 というものを見つけました。

さっそく Kotlin で実装、 完成しました。

647_製作過程A

自分で作っておいて感動しました。 画像の黄色い四角が表示されてるところが、 A* アルゴリズムで導き出した最短距離です。

さて、 できたのは良いんですが、 問題点があります。 鋭い方ならもう気づいているかもしれませんが、 画像の左上をご覧ください。 FPS が 25 くらいになってます。 つまり、 この処理、 重いんです。 毎フレームこの処理を行うと、 1 つの最短経路を計算するだけで、 これだけ FPS が落ちてしまいます。

A* は処理量が多いのがデメリットと言われているみたいですが、 実装してみるとその通りでした。 うまく最適化するのが今後の目標になりそうです。


comment ×0
10 / 28
Mon

薄々気づいてたんですが、 マップの描画処理が完成した以上、 キャラチップを描かないと開発が進みませんよね・・・。 人物描くのがものすごく苦手なんですが、 良い方法ないでしょうか・・・。

それだけです。


comment ×0
10 / 26
Sat

定期的にドット絵を描かないと腕がなまりそうなので、 自分の部屋のフローリングをマップチップにしてみました。

643_フローリング

マップチップは背景だから、 目立ちすぎないようにしないといけないので、 気を使います。


comment ×0
10 / 24
Thu

Scala とか Groovy とかに触れてみて、 JVM 上で動く言語に興味をもったので、 片っ端から勉強してました。 ちなみに 「勉強」 というのは、 言語使用を一通り読むことです。

これまで勉強した JVM 言語は、 Scala, Groovy, Gosu, Clojure, JRuby, Mirah, Xtend, Kotlin, Ceylon, Fantom の 10 個です。 このうち、 パフォーマンスや自分の好みとかを考慮したうえで、 実際にダウンロードして、 マップ描画の処理を実際に書いてみたのが、 Scala, Groovy, Gosu, Kotlin の 4 言語です。

で、 これまでは Scala で書いてたんですが、 Kotlin で書いていこうかなと思っています。 コードの見た目が好きです。 パフォーマンスとか知名度とかより、 見た目を重視してしまうんです。 やっぱり、 自分が好きなコードがかけないと、 モチベーションが保てませんよ。


comment ×0
10 / 21
Mon

ここでオートタイルの処理を行った画像を上げましたが、 オートタイルの処理のコードはこんな感じになってます。

def autotileNumber(x: Int, y: Int): Array[Int] = {
  val direction: Array[Array[Int]] = Array(Array(x - 1, y - 1), Array(x + 1, y - 1), Array(x - 1, y + 1), Array(x + 1, y + 1),
                                           Array(x - 1, y + 1), Array(x + 1, y + 1))
  val number: Array[Array[Int]] = Array(Array(5, 11, 4, 1, 0), Array(5, 15, 6, 1, 2), Array(5, 3, 4, 9, 8),
                                        Array(5, 7, 6, 9, 10), Array(16, 16, 16, 13, 12), Array(16, 16, 16, 13, 14))
  var resultArray: ArrayBuffer[Int] = ArrayBuffer()
  for (i <- 0 to 5) {
    val fx: Int = direction(i)(0)
    val fy: Int = direction(i)(1)
    var result: Int = 16
    (isAutotile(fx, fy), isAutotile(fx, y), isAutotile(x, fy)) match {
    case (true, true, true) =>
      result = number(i)(0)
    case (false, true, true) =>
      result = number(i)(1)
    case (_, false, true) =>
      result = number(i)(2)
    case (_, true, false) =>
      result = number(i)(3)
    case _ =>
      result = number(i)(4)
    }
    resultArray += result
  }
  return resultArray.toArray
}

返される値は、 左上の天井, 右上の天井, 左下の天井, 右下の天井, 左の壁, 右の壁のオートタイル番号を順に格納した配列です。 オートタイル番号というのは、 この記事の 2 枚目の画像で表示されている番号です。

同じ処理を 2 年くらい前に Ruby で書いたときよりコード量がかなり減っています。 たぶん半分くらい。 まあ、 2 年前のコードは無駄が多かったというのもあるんですが、 Scala のパターンマッチのおかげで、 かなり綺麗に短くコードが書けました。 ・・・その代わりに、 簡略化しすぎて direction とか number とかの変数が何を表してるのか分かりにくくなりましたけどね。

あ、 そうそう、 Scala には型推論という非常に強力な機能がありますが、 私は全然利用してません。 全ての変数に型を明示してます。 何か書かないと落ち着かないんですよ・・・。


comment ×0
10 / 20
Sun

ドット絵も Scala も始める前に作ったデータベースをちょっと公開したいと思います。 たぶん、 作っている最中でかなり変更されていくと思うので、 ネタバレにはならないと思います。

637_データベース

データの一部です。 簡単に説明すると、 例えば 「ロングソード」 の欄を見ると、 面 2, 威力 24, 追加 7 となってるんですが、 これは Elona でいう 「2d24+7」 です。 つまり、 1 から 24 までの数がランダムで出るサイコロを 2 回振って、 出た目の和に 7 を加えた値が、 この武器の攻撃力になります。 つまり、 この武器は最大で 2×24+7=55 の攻撃力になり、 最小で 2×1+7=9 の攻撃力になります。

ちなみに、 武器は素材によって威力が変わります。 例えば素材の革は、 威力 0.6 倍, 追加 0.3 倍なので、 革のロングソードの攻撃力は 2d14+2 になります。 一方で、 素材のアダマンタイトは、 威力 4 倍, 追加 1.6 倍なので、 アダマンタイトのロングソードの攻撃力は 2d96+11 で、 最大 203 になります。 まあ、 Elona とほぼ同じシステムですね。

というか、 アダマンタイト製モールはやばいですね。 1d320+0 ですよ。 それに加え防御力無視の貫通効果が 80% の確率で発生しますし、 やばい武器です。 まあ、 その代わり、 命中率が 15% と異様に低いし、 最高攻撃力の 320 が出る確率は 1/320 なんですけどね。

一方で、 ダイアモンド製メイスなんかは 2d3+160 で、 攻撃力は 163~166 でかなり安定します。 これは魔法使い用なんで、 打撃ではあまり使わないでしょうけど・・・。


comment ×0
10 / 19
Sat

私のことをここに全然書いてなかったので、 ゲームの製作過程から離れて、 プログラミング歴について書いてみます。

最初に触ったプログラミング言語は BASIC でした。 簡単な数字当てゲーム的なのを作って遊んでたんですが、 そのうち物足りなくなって、 HSP に手を出しました。 HSP は 2 年くらい使ってたのかな・・・。 タイピングとかシューティングとか、 結構ゲームを作ってた気がします。 残念ながら今はもうデータが残っていなくて、 どんなゲーム作ったのか記憶にしか残ってないんですが・・・。 そしてその記憶も曖昧。

次は Ruby です。 オブジェクト指向に初めて出会って、 最初は戸惑いました記憶があります。 でも、 慣れれば便利なんですよね。 Ruby は 4 年前くらいから愛用してます。 今はメインの開発言語ではないんですが、 ちょっとした処理を自動化したいときなんかはよく Ruby を使います。

で、 Ruby をやっている間に Java にも手を出したんですが、 手を出しただけで終わりました。 というのも、 文法というかコードの見た目というかが、 あまり気に入らなかったんですよね。 動的型付け言語の Ruby を先に習得したので、 静的型付け言語の Java が面倒に感じた覚えもあります。 あ、 あのメソッドとか変数名の getGraphics みたいな、 2 単語目以降の単語の先頭を大文字にするってのが、 気に食わなかったですね。 いや、 だったら 1 単語目も大文字にしろよ、 不平等だろ、 みたいな。

そんなわけで、 Java にちょっかいを出しつつ Ruby を使ってたんですが、 そろそろ Java も本格的に学んでみようと思ったのが、 今年の 9 月です。 でも、 やっぱり Java には不満があって、 嫌になってきて、 Ruby に戻ろうとしたときに現れたのが Scala です。

そんな感じで、 今はメインに Scala を使ってます。 関数もオブジェクトって概念が好きです。 関数型プログラミングってのは始めて触れるので、 まだよく分かってないんですが・・・。

ちなみに、 私が今それなりに使える言語は、 Ruby, Python, Java, Scala でしょうか。 HSP はもうほとんど忘れました。 突然出てきた Python は、 Ruby やってるときに友達になりました。


comment ×0
10 / 19
Sat

Java 派生の言語に Groovy ってやつを見つけたので、 これまでのプログラムを書き直してみました。 プログラムの見た目が Ruby に似ていて、 これは良いんじゃないかと思いつつ、 全部のプログラムを書き終えて、 実行しようとすると、 なんと FPS が 27。 いやいや、 処理オーバーしてるじゃないですか。 スクリプト言語というだけあって、 やっぱり実行速度は遅いんでしょうか。 Typed アノテーションを使う Groovy++ とやらを使えば少し速くなるらしいんですけどね。

そんなこんなで、 Groovy は解雇されました。 やっぱり Scala かなぁ・・・。 Ruby はスクリプト言語で決して速いわけではないし、 Java はプリミティブ型が嫌いだし・・・。

といっても、Scala も実はまだよく分かってないんですよね。 1 冊 Scala の本を買って読みたいんですけど、 なかなか時間が取れないんですよね。


comment ×0
10 / 16
Wed

せっかくマップチップを増やしたので、 実際にマップとして表示してみました。

633_製作過程A

520 とか 40 とか書いてあるマスは、 まだマップチップを描いてないところです。 左側の草原の方は、 なんだか充実してきましたね。 右側の洞窟っぽい方は、 壁タイル (520) がまだ描いてないので、 次に描くことにします。

あ、 そういえば以前 FPS が指定した値で安定しないとか嘆きましたが、 まだ解決してません。 右上に出てる数字が FPS なんですが、 見ての通り 37 です。 設定値は 40 なんですがね・・・。


comment ×0
10 / 16
Wed

草原のマップチップを増やして、 土のマップチップを新しく描きました。

633_土

土のザラザラ感を出すのって難しいんですね。 他の方が描いたマップチップを参考に何とかやってみたんですが、 それなりにはうまくいったと思います。

これまで、 RPG ツクールの素材とか Elona のデータとかを参考にしてたんですが、 いつかは、 何も見ずにドット絵を描けるようになりたいものです。


comment ×0
10 / 13
Sun

マップチップ描いたので、 実際にどんな風に表示されるかテストしてみました。 ついでに、 家の壁 (というか屋根) のマップチップも描いて、 一緒に表示させました。

630_製作過程B

実は、 屋根の部分にオートタイルの処理を施してあります。 要するに、 自動的に隣接するタイルとの境界を調整してくれるわけです。 この処理は、 前に一度 Ruby で実装したことがあったので、 それを Scala に書き直すだけで良かったんですが、 Scala のパターンマッチのおかげでかなり簡潔な記述ができました。 パターンマッチ、 偉大ですね。

バツ印に数字が書いてある部分は、 タイル画像が見つからなかった場合の表示です。 今後、 ドット絵よりもシステムが先行して完成した場合、 エラーにならないようにするための処理です。

さて、 オートタイルについていまいちピンと来ない方は、 デバッグ時のこの画像を見れば分かりやすいんじゃないでしょうか。

630_製作過程A

番号が 0 から 15 までのタイル画像が用意されていて、 境界がきれいになるように、 自動で適切に並べてくれるわけです。


comment ×0
10 / 12
Sat

Scala ばっかりだったので、 ドット絵を描きました。 今度はアイテムとか設置物とかではなく、 マップチップです。

628_草原

マップチップの基本、 草原なのです。 この緑の色合いを出すのが意外と難しかったりそうでもなかったり・・・。 ついでだったんで、 石が露出してるものと花が咲いてるものも描きました。

もう少しマップチップを描いたら、 実際にゲーム画面に並べてみたいですねー。


comment ×0
10 / 06
Sun

また Scala です。 Java にはない新しいことがたくさんあるので、 調べてると飽きません。 そろそろドット絵も描かないとですね・・・。

RPG のマップを管理するクラスを作ってました。 タイルデータは 2 次元配列で管理しています。 配列の長さとか内容とかが変化する可能性があるので、 scala.collection.mutable パッケージにある ArrayBuffer クラスを使っています。 まず、 クラス内でこんな感じで初期化されます。

private var map: ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer[ArrayBuffer[Int]]

で、 コンストラクタで外部ファイルを読み込んで、 タイルデータを格納していきます。

val source: Source = Source.fromFile("data/data/map/" + number + "-" + floor + ".txt")
val regexData: Regex = "([\\d\\s,]+)".r
try {
  for (line <- source.getLines) {
    line match {
    case regexData(data) =>
      map += ",".r.split(line).to[ArrayBuffer].map((string: String) => string.trim.toInt)
    case _ =>
    }
  }
} finally {
  source.close
}

この段階ではわざわざ case ~ match 構文を使う必要はないんですが、 今後ファイルから読み込むデータが増えたときに簡単に追加できるよう、 この構文を使って書いておきました。 あ、 number と floor は、 それぞれマップ番号と (ダンジョンなどの) 階層が格納してある変数です。

map += ~ の部分が複雑になっちゃったんですよね。 まず、 split メソッドでコンマごとに区切ったリストを生成し、 それを to メソッドで ArrayBuffer オブジェクトに変換した後、 map メソッドで要素を Int オブジェクトに変換してます。 (string: String) => string.trim.toInt_.trim.toInt と書けばそれで十分ですが、 アンダースコアはあまり使いたくないのです。

そうそう、 最初は string.trim.toIntstring.toInt だけにしていたので、エラー吐かれて困ってました。 どうやら先頭に空白があると変換できないみたいですね。 融通が利かないなぁ・・・。 Ruby では " 12".to_i はちゃんと機能しましたよ?

あ、 あと case _ => も書き忘れてて困ってました。 case ~ match 構文はパターンが網羅されてないとだめっぽいです。


comment ×0
10 / 06
Sun

先日紹介した replaceAll メソッド、 「便利じゃないですか」 とか言っていてましたが、 やっぱり便利で需要があるようで、 Scala の scala.util.matching.Regex クラスに replaceAllIn メソッドの形で、 すでに実装されていました。 いや、 私の苦労は何だったんだ・・・。 もともと Java 使ってたので、 Scala の正規表現についてはよく知らなかったんですよね。

ということで、 正規表現について調べてみました。 まず、 文字列から正規表現にマッチした部分を取り出す処理です。 あ、 事前に scala.util.matching.Regex と scala.util.matching.Regex.Match はインポートしておく必要があります。

val string: String = "Many kinds of wild animals have been disappearing"
val regex: Regex = "\\w+".r
for (matched <- regex.findAllIn(string).matchData) {
  println("\"" + matched.matched + "\" at " + matched.start)
}

パターンマッチとかいろいろできるみたいですが、 Scala にまだ慣れてないせいでコードがいまいちよく分からないんですよね。 分かりしだい、 ここにメモしておきます。


comment ×0
10 / 05
Sat

Java の String クラスには replaceAll というメソッドがあって、 これが正規表現を使えるので便利なんです。 例えば、 こんな感じです (Scala で書いてあります)。

val string = "1234abc56de789fgh0"
val result = string.replaceAll("\\d+", "N")
println(result)

実行すると NabcNdeNfghN が表示されます。

さて、 なかなか便利なんですが、 1 つ欠点があります。 例えば、 正規表現にマッチした部分が 100 以上だったら M に置換し、 そうでなかったら N に置換したい場合はどうすれば良いでしょうか。 replaceAll メソッドは、 置換先は固定の文字列なので、 こういったことはできません。 そこで、 Scala の特長の 1 つである引数に関数をとれることを利用して、 replaceAll メソッドをオーバーロードしてみます。

def replaceAll(regex: String, replacer: String => String): String = {
  val matcher: Matcher = Pattern.compile(regex).matcher(string)
  val buffer: StringBuffer = new StringBuffer
  while (matcher.find) {
    matcher.appendReplacement(buffer, replacer(matcher.group))
  }
  matcher.appendTail(buffer)
  return buffer.toString
  }
}

このメソッドを使うと、 先に挙げた例も実現できます。

val string = "1234abc56de789fgh0"
val result = string.replaceAll("\\d+", (matched: String) => {
  if (matched.toInt >= 100) {
    "M"
  } else {
    "N"
  }
})
println(result)

実行すると MabcNdeMfghN が表示され、 正しく置換されていることが分かります。

かなり便利じゃないですか? しかも、 Ruby と同じように書けるところが好きです。 ちなみに、 Ruby で同じことをするとこんな感じになります。

string = "1234abc56de789fgh0"
result = string.gsub(/\d+/) do |matched|
  if (matched.to_i >= 100)
    "M"
  else
    "N"
  end
end
puts(result)

comment ×0
10 / 04
Fri

Scala っていうプログラミング言語を見つけました。 これがあまりに素晴らしかったんで、 今後の開発環境は Java から Scala に変更することにしました。

Java にはプリミティブ型というものがあって、 正直私にとってはこれは受け入れがたかったんです。 慣れ親しんだ Ruby のような完全なオブジェクト指向言語だと、 オブジェクトでないものは存在しないからです。 でもかといって、 Ruby は Java と比べて 10 倍遅いですし、 ゲーム用のライブラリもそんなにない。 そこで、 Scala を発見したわけです。

まず Scala は Java プラットフォーム上で動くので、 速度は Java とほぼ同じくらい。 そのおかげで Java との連携も簡単で、 普通に JFrame とか JPanel とかが使える。 加えて完全なオブジェクト指向。 コードの見た目も気に入っていた Ruby に似ていて、 私が受け入れやすい。 他にも様々な理由があるのですが、 もう Scala 一択です。

Java を受け継ぎながら、 私が Java の嫌いな点を消し去った感じです。 素晴らしいです。


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