2013/12/05(木) [n年前の日記]
#3 [dxruby][game] DXRubyで二足歩行する敵っぽい動きを作ってみる
今日も DXRuby を使って何かしら書いてみます。
今回も多関節ネタということで、二足歩行する敵っぽい動きを試してみようかなと。
なんで二足歩行なのかというと…。個人的に、ちょっと思い入れがありまして…。
昔、先輩から、「このゲームの、この中ボスは、こうやって動かしてるんだよ」と説明してもらったことがありまして。「うわー、面白そう。自分もそういうのやってみたい」と思ったものの、その後、そんな機会には恵まれず。
当時は、その手のゲームを作るとなるとアセンブラで書くのが当たり前だったので、なかなか気軽に試すわけにもいかなくて。また、CGツールも無かった時代ですので、絵描きさんに「こういうの描いてくれませんか?」とお願いする必要もあって。 *1 なので、お遊びでチョチョイッと、てなわけにはいかなかったのです。 *2
だけど今なら、気軽に試せる DXRuby がある。CGツールだって、好きなだけ使える。ということで、当時、指をくわえて眺めていたあの動きを、自分もちょっと試しに書いてみようかな、と。
とりあえず、こんな感じになりました。
ざっくりと解説を。
一般的に、2Dゲームの多関節処理というと、各関節毎に角度を管理・制御して動きをつけていくのですけど。このプログラムでは、少し違うことをしています。
このプログラムでは、各関節の角度ではなく、根元と先っぽ(足先)の座標だけを管理・制御しています。そして、膝だか肘だかの座標は、計算して求めます。
この仕組みを作ってしまえば、根元と先っぽだけを制御すればいいので、比較的自由に動きをつけていけますし、時々、プログラマーが意図してなかったポーズになったりもして、見た目が面白い場面も出てきたり…するかもしれません。
MMD(MikuMikuDance)や3DCGソフトを触ったことがある人なら、ピンときたのではないでしょうか。「それって、IK(インバースキネマティクス)じゃね?」。そうです。これ“も”、IKです。
MMDや3DCGソフトのIKは、以下のような状態ですが…。
さて、実際の処理内容ですが…。
ちょっと分かりづらいかな…?。余裕があったら説明図も後で描いときます。
(※ 2013/12/06追記。説明図を追加してみました。ちょっとだけ…分かりやすくなった…のかどうか…)
ところで。
DXRuby は、スプライトの回転拡大縮小半透明描画ができますので…。何もわざわざ、複数のスプライトを数珠繋ぎにして、足を描かなくてもいいような気がしてきました。
ということで、足を丸々画像にして、回転させて描画してみましょうか。blender でレンダリングした画像を使ってみました。
ただ、見た目をリアルにしていくのは楽しい作業なのですが…。これはちょっと、落とし穴がありそうな気もしますね。
まあ、今回は、「昔憧れたあの動きを、DXRubyという便利ライブラリを使って気軽に実験」という話でやってますので、コレはコレでOKかなと。
ソースと画像も置いときますね。Public Domainってことで。
_walkenemy.zip
さて、明日も何か書いてみようかな…と思ったけれど、今日のコレで時間ギリギリだったので、二日に1回ぐらいのペースで書くかもしれないし書かないかもしれません。まあ、この手の記事を書く練習ってことで、気楽に、気楽に…。
今回も多関節ネタということで、二足歩行する敵っぽい動きを試してみようかなと。
なんで二足歩行なのかというと…。個人的に、ちょっと思い入れがありまして…。
昔、先輩から、「このゲームの、この中ボスは、こうやって動かしてるんだよ」と説明してもらったことがありまして。「うわー、面白そう。自分もそういうのやってみたい」と思ったものの、その後、そんな機会には恵まれず。
当時は、その手のゲームを作るとなるとアセンブラで書くのが当たり前だったので、なかなか気軽に試すわけにもいかなくて。また、CGツールも無かった時代ですので、絵描きさんに「こういうの描いてくれませんか?」とお願いする必要もあって。 *1 なので、お遊びでチョチョイッと、てなわけにはいかなかったのです。 *2
だけど今なら、気軽に試せる DXRuby がある。CGツールだって、好きなだけ使える。ということで、当時、指をくわえて眺めていたあの動きを、自分もちょっと試しに書いてみようかな、と。
とりあえず、こんな感じになりました。
# 二足歩行する敵モドキ require 'dxruby' $angadd = 2 # 足先の座標を求めるクラス class Foot def initialize(start_ang) @ang = start_ang @x = 0 @y = 0 self.update end attr_accessor :x, :y def update # 円運動をさせてみる。 # 本当は放物線運動のほうがいいと思う…けど # 面倒臭いから今回は安直に円運動 ang = @ang * Math::PI / 180.0 @x = -128 * Math.cos(ang) @y = 96 * Math.sin(ang) @y = 0 if @y >= 0 @ang += $angadd end end # 棒状の部分を複数のスプライトを使って描く def draw_foot_sub(bx, by, tx, ty, img) count = 10 count.times do |i| x = bx + ((tx - bx) * i / (count - 1)) - img.width / 2 y = by + ((ty - by) * i / (count - 1)) - img.height / 2 Window.draw(x, y, img) end end # 足を一本分描画する def draw_foot(bx, by, tx, ty, img, flen) dw = tx - bx dh = ty - by # 腰から足の先までの長さ、の半分を求める # 三平方の定理(ピタゴラスの定理)を使ってる l = Math.sqrt(dw * dw + dh * dh) / 2 # 膝までの距離を求める。これも、三平方の定理を使ってる nl = flen * flen - l * l n = (nl <= 0)? 0 : Math.sqrt(nl) # 腰と足先の中間点を求める cx = (bx + tx) / 2 cy = (by + ty) / 2 # 膝があるはずの方向(角度)を求める # 腰と足先の座標から角度を求めて、それに90度足せばいい ang = Math.atan2(dh, dw) + Math::PI / 2 # 膝の場所を求める lx = cx + n * Math.cos(ang) ly = cy + n * Math.sin(ang) # 腰から膝までと、膝から足先までを描く draw_foot_sub(bx, by, lx, ly, img) draw_foot_sub(lx, ly, tx, ty, img) # 分かりやすくするために線を引いてみる Window.drawLine(cx, cy, lx, ly, [255, 0, 0]) end img = Image.load("ufo.png") by = 150 # 腰の高さ gy = 420 # 地面の高さ # 足半分の長さ flen = (gy - by) / 2 + 20 footl = Foot.new(0) footr = Foot.new(180) # メインループ Window.loop do break if Input.keyPush?(K_ESCAPE) # マウスカーソルのy座標で足先の移動速度を変えてみる $angadd = 20.0 * Input.mousePosY / Window.height - 10.0 bx = Input.mousePosX # 右足の根元と先っぽの座標を求める n = 24 frbx = bx - n frby = by frtx = bx + footr.x - n frty = footr.y + gy # 左足の根元と先っぽの座標を求める flbx = bx + n flby = by fltx = bx + footl.x + n flty = footl.y + gy # 右足を描く draw_foot(frbx, frby, frtx, frty, img, flen) # 腰を描く Window.drawScale(bx - img.width / 2, by - 80 , img, 2, 2) # 左足を描く draw_foot(flbx, flby, fltx, flty, img, flen) # 足先の座標を更新 footl.update footr.update # 分かりやすくするために線を引いてみる Window.drawLine(frbx, frby, frtx, frty, [0, 255, 255]) Window.drawLine(flbx, flby, fltx, flty, [0, 255, 255]) endちょっとソースが長くなってしまいましたが…。もうちょっと短く書けないのかな、コレ…。
ざっくりと解説を。
一般的に、2Dゲームの多関節処理というと、各関節毎に角度を管理・制御して動きをつけていくのですけど。このプログラムでは、少し違うことをしています。
このプログラムでは、各関節の角度ではなく、根元と先っぽ(足先)の座標だけを管理・制御しています。そして、膝だか肘だかの座標は、計算して求めます。
この仕組みを作ってしまえば、根元と先っぽだけを制御すればいいので、比較的自由に動きをつけていけますし、時々、プログラマーが意図してなかったポーズになったりもして、見た目が面白い場面も出てきたり…するかもしれません。
MMD(MikuMikuDance)や3DCGソフトを触ったことがある人なら、ピンときたのではないでしょうか。「それって、IK(インバースキネマティクス)じゃね?」。そうです。これ“も”、IKです。
MMDや3DCGソフトのIKは、以下のような状態ですが…。
- 3Dで計算する。
- 各関節(?)の長さは自由。
- 計算で求める関節は、複数ある。
- 2Dで計算する。(平面上の座標計算しかしない。)
- 各関節の長さは同じであること。
- 計算で求める関節は、一つだけ。
さて、実際の処理内容ですが…。
- 根元と先っぽの座標から、根元から先っぽまでの距離を求めます。これは、三平方の定理(ピタゴラスの定理)で求められますね。
- 膝があるはずの座標は、根元と先っぽを結ぶ線の、中間の座標から垂直に延びる線上にあるはずです。ですので、中間点の座標も求めておきます。
- 膝があるはずの方向は、根元と先っぽの座標から角度を求めて、その角度に90度足した角度になるはずです。
- 膝までの距離も、三平方の定理で求められます。根元と先っぽの距離は分かってますし、関節の長さは固定のはずですから。平方根(Math.sqrt())を使うあたり、処理速度が不安ですが、今時のPCなら…ねえ…。呼ぶ回数も、1フレームで数回ですし。まあ、大丈夫じゃないかなと。
ちょっと分かりづらいかな…?。余裕があったら説明図も後で描いときます。
(※ 2013/12/06追記。説明図を追加してみました。ちょっとだけ…分かりやすくなった…のかどうか…)
ところで。
DXRuby は、スプライトの回転拡大縮小半透明描画ができますので…。何もわざわざ、複数のスプライトを数珠繋ぎにして、足を描かなくてもいいような気がしてきました。
ということで、足を丸々画像にして、回転させて描画してみましょうか。blender でレンダリングした画像を使ってみました。
# 二足歩行する敵モドキ。足を画像にしてみた版 require 'dxruby' $angadd = 2 # 足先の座標を求めるクラス class Foot def initialize(start_ang) @ang = start_ang @x = 0 @y = 0 self.update end attr_accessor :x, :y def update # 円運動をさせてみる。 # 本当は放物線運動のほうがいいと思う…けど # 面倒臭いから今回は安直に円運動 ang = @ang * Math::PI / 180.0 @x = -128 * Math.cos(ang) @y = 96 * Math.sin(ang) @y = 0 if @y >= 0 @ang += $angadd end end # 棒状の部分を1枚のスプライトを回転させて描く def draw_foot_sub(bx, by, tx, ty, img) cx = (tx + bx) / 2 - img.width / 2 cy = (ty + by) / 2 - img.height / 2 dw = tx - bx dh = ty - by ang = Math.atan2(dh, dw) * 180.0 / Math::PI Window.drawRot(cx, cy, img, ang) end # 足を一本分描画する def draw_foot(bx, by, tx, ty, img, flen) dw = tx - bx dh = ty - by # 腰から足の先までの長さ、の半分を求める # 三平方の定理(ピタゴラスの定理)を使ってる l = Math.sqrt(dw * dw + dh * dh) / 2 # 膝までの距離を求める。これも、三平方の定理を使ってる nl = flen * flen - l * l n = (nl <= 0)? 0 : Math.sqrt(nl) # 腰と足先の中間点を求める cx = (bx + tx) / 2 cy = (by + ty) / 2 # 膝があるはずの方向(角度)を求める # 腰と足先の座標から角度を求めて、それに90度足せばいい ang = Math.atan2(dh, dw) + Math::PI / 2 # 膝の場所を求める lx = cx + n * Math.cos(ang) ly = cy + n * Math.sin(ang) # 腰から膝までと、膝から足先までを描く draw_foot_sub(bx, by, lx, ly, img) draw_foot_sub(lx, ly, tx, ty, img) # 分かりやすくするために線を引いてみる # Window.drawLine(cx, cy, lx, ly, [255, 0, 0]) end img = Image.load("foot.png") bodyimg = Image.load("body.png") by = 150 # 腰の高さ gy = 420 # 地面の高さ # 足半分の長さ flen = (gy - by) / 2 + 20 footl = Foot.new(0) footr = Foot.new(180) # メインループ Window.loop do break if Input.keyPush?(K_ESCAPE) # マウスカーソルのy座標で足先の移動速度を変えてみる $angadd = 20.0 * Input.mousePosY / Window.height - 10.0 bx = Input.mousePosX # 右足の根元と先っぽの座標を求める n = 24 frbx = bx - n frby = by frtx = bx + footr.x - n frty = footr.y + gy # 左足の根元と先っぽの座標を求める flbx = bx + n flby = by fltx = bx + footl.x + n flty = footl.y + gy # 右足を描く draw_foot(frbx, frby, frtx, frty, img, flen) # 腰を描く Window.draw(bx - 150, by - 100, bodyimg) # 左足を描く draw_foot(flbx, flby, fltx, flty, img, flen) # 足先の座標を更新 footl.update footr.update # 分かりやすくするために線を引いてみる # Window.drawLine(frbx, frby, frtx, frty, [0, 255, 255]) # Window.drawLine(flbx, flby, fltx, flty, [0, 255, 255]) endうむ。実にそれらしくなりましたな。
ただ、見た目をリアルにしていくのは楽しい作業なのですが…。これはちょっと、落とし穴がありそうな気もしますね。
まあ、今回は、「昔憧れたあの動きを、DXRubyという便利ライブラリを使って気軽に実験」という話でやってますので、コレはコレでOKかなと。
ソースと画像も置いときますね。Public Domainってことで。
_walkenemy.zip
さて、明日も何か書いてみようかな…と思ったけれど、今日のコレで時間ギリギリだったので、二日に1回ぐらいのペースで書くかもしれないし書かないかもしれません。まあ、この手の記事を書く練習ってことで、気楽に、気楽に…。
◎ 余談。他のツールを検討すべき境界線。 :
DXRuby に限らず、2Dゲームライブラリを使ってアレコレ作っていく際に、見た目をリアルにしていくことにリソースを割いていくと…。
どこかの時点で、「ソレ、Unity使ったほうがよくね?」となりそうな気がするのです。つまり、そこに落とし穴というか、「ここから先は別のツールを使ったほうが…」と判断できる境界線が、どこかにあるんじゃないのかな、と思えるのですね。
Unity の場合…。
ただ、Unityも、習得に要する時間というものがあるわけで…。
習得時間に関しては、DXRuby 等、2Dゲームライブラリは、比較的短いので…。やはり、「ちょっと試してみる」「この手のプログラムの書き方を勉強してみる」という点では、メリットがあるようにも思うのでした。
どこかの時点で、「ソレ、Unity使ったほうがよくね?」となりそうな気がするのです。つまり、そこに落とし穴というか、「ここから先は別のツールを使ったほうが…」と判断できる境界線が、どこかにあるんじゃないのかな、と思えるのですね。
Unity の場合…。
- 3Dで描画するから、見た目からしてリアル。
- 多関節キャラを扱うのは当たり前。そもそも、人体モデルが多関節キャラですから…。
- 多関節キャラの動きを、3DCGソフトを使って確認しながら作り込める。トライ&エラーが簡単。
- モーションの合成も、基本機能として持っている。
- IKも、基本機能として持っている…。
ただ、Unityも、習得に要する時間というものがあるわけで…。
習得時間に関しては、DXRuby 等、2Dゲームライブラリは、比較的短いので…。やはり、「ちょっと試してみる」「この手のプログラムの書き方を勉強してみる」という点では、メリットがあるようにも思うのでした。
*1: 当時は、国民機 PC-9801 を使ってた時代ですから…。16色しか出ませんから…。TVゲーム機用の色を出すには、自社設計のハードウェアをPC-9801に追加して、とかやってた時代ですから…。プログラマーが、余技? 趣味? でCGツールも使わせてもらう、なんてことは、ちょっと難しかったのです。…いや、やる気ビンビンな先輩方は「俺にもCGツール使わせて!」とかやってましたけど。自分は引っ込み思案、かつ、プログラム書くだけで手一杯でそこまでは…。
*2: そもそも自分、当時はまともなPCを持ってなかった気がする。X1 turbo は部屋にあったかもしれないけど、電源すら入れてなかったような…。X68Kでも持ってたら違ったのかもしれないけど。
*2: そもそも自分、当時はまともなPCを持ってなかった気がする。X1 turbo は部屋にあったかもしれないけど、電源すら入れてなかったような…。X68Kでも持ってたら違ったのかもしれないけど。
[ ツッコむ ]
以上です。